艺术成分很高的完全自定义的UITabBar(很简单)

引言

在iOS应用开发中,UITabBar是一个非常场景且重要的UI组件。系统为我们提供的UITabBar虽然功能强大,但是在某些情况下,它的标准样式并不能满足我们特定的设计需求,它的灵活性也有一些局限。为了打造更具个性化好的用户友好的交互,我们十分有必要了解该如何自定义UITabBar。

这篇博客我们将探讨如何通过代码来定制UITabBar,如果修改外观,如何添加动画,以及更加复杂的交互等等,希望能够帮助大家打造更加独特更加吸引人的iOS应用页面。

系统UITabBar的痛点

默认的UITabBar虽然创建的过程可以为我们省略很多代码,但是它的缺点也十分明显,有时候我们甚至需要写更多的代码来弥补它的不足,还有时候我们甚至对它束手无策。

  1. 定制型有限:首先它的外观和布局是固定,几乎无法满足任何特定的设计需求,比如最常见的凸出式的Tab按钮。
  2. 动画效果欠缺:系统为我们提供的UITabBar缺乏炫酷的动画效果,如果想要实现自定义的过渡动画和交互可能需要写大量的代码。
  3. 扩展性比较差:通常我们还会为按钮添加很多特别的标签,比如消息数量,新功能上线小红点,或者是用户徽章等,如果在系统提供的UITabBar的按钮上添加会非常麻烦。
  4. 交互设计局限:系统的UITabBar在交互设计上十分简单,只有点击事件,如果想要增加长按或者双击或者其他交互并不容易。
  5. 页面切换管理麻烦:当我们切换到一个不需要TabBar的页面还需要自己来管理它的隐藏和显示,有的时候动画显得不流畅。

自定义UITabBar

要解决系统UITabBar的各种痛点,我们需要进行自定义。这通常包括更改外观、添加动画效果、动态管理标签、扩展功能以及优化响应式设计。在自定义UITabBar时,常见的做法是继承UITabBar类,利用其现有的方法和属性来进行扩展和修改。

然而,为了最大限度地提升灵活性和可扩展性,我们可以直接继承UIView来创建自己的TabBar。这样可以完全掌控TabBar的布局和行为,从而更灵活地实现定制需求。

接下来我们将展示如何通过继承UIView来创建一个自定义的TabBar。通过这种方法,我们可以:

  1. 完全自定义外观:任意修改TabBar的形状、颜色、大小和背景。
  2. 添加自定义动画:为选项卡切换添加炫酷的过渡动画和交互效果。
  3. 扩展功能:在TabBar中添加通知徽章、特殊按钮或其他自定义控件。
  4. 丰富交互设计:实现复杂的交互效果,例如双击切换标签、长按弹出菜单等。

准备工作

为了让继承自UIView的TabBar生效,首先我们要自定义UITabbarController,不过这并不需要大费周章,因为我们的目的仅仅是为了隐藏它自带的系统TabBar。

我们就继承自UITabbarController创建一个名为CSCustomTabBarVC的子类,并且通过从写它的viewDidLoad方法来隐藏系统的UITabBar具体代码如下:

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .white
        hiddenRealTabbar()
    }

    //MARK: 隐藏tabbar
    private func hiddenRealTabbar() {
        for view in view.subviews {
            if view.isKind(of: UITabBar.self) {
                view.isHidden = true
                break
            }
        }
    }

完全自定义外观

开始自定义TabBar,继承自UIView创建了一个名为CSTabbarView的类,我们开始来为它自定义外观,就按照上面的案例为它添加5个按钮,(注意虽然它不需要和你的页面对应,但至少要和功能对应奥)。

class CSTabbarView: UIView {
    
    /// 当前选中button
    var selectedButton:UIButton? = nil
    /// 渐变
    var gradientView = CLGradientView(startColor: UIColor.white.withAlphaComponent(0), endColor:UIColor.white.withAlphaComponent(1.0), direction: .topToBottom)
    /// 背景
    var backView = UIView()
    /// 第一个按钮
    let firstButton = UIButton()
    /// 第二个按钮
    let secondButton = UIButton()
    /// 开播按钮
    let startLiveButton = UIButton()
    /// 第三个按钮
    let thirdButton = UIButton()
    /// 第四个按钮
    let fourthButton = UIButton()
}

为了让代码更直观,我们将所有的按钮都列举了出来,当然我们可以通过数据循环去创建这些按钮(通常来讲,通过数据来创建会更灵活一些)。

这里还为TabBar添加了一个渐变的背景颜色,具体代码就不贴出了采用的是特殊图层CAGradientLayer,关于这个图层在核心动画专栏中也有过介绍。

接下来就来添加和布局这些视图和按钮,具体代码如下:

    func setupView() {
        // 渐变
        self.addSubview(gradientView)
        // 背景
        self.addSubview(backView)
        backView.backgroundColor = .white
        backView.layer.cornerRadius = (cs_tabbarHeight-cs_bottomInset)/2.0
        backView.layer.shadowOffset = CGSize(width: 0, height: 3)
        backView.layer.shadowColor = UIColor.black.withAlphaComponent(0.1).cgColor
        backView.layer.shadowOpacity = 1
        backView.layer.shadowRadius = 40
        // 第一个按钮
        backView.addSubview(firstButton)
        firstButton.tag = 100
        // 第二个按钮
        backView.addSubview(secondButton)
        secondButton.tag = 101
        // 直播按钮
        self.addSubview(startLiveButton)
        startLiveButton.setImage(UIImage(named: "tabbar_broadcast_icon"), for: .normal)
        // 第三个按钮
        backView.addSubview(thirdButton)
        thirdButton.tag = 102
        // 第四个按钮
        backView.addSubview(fourthButton)
        fourthButton.tag = 103
    }

布局代码:

    func setLayout() {
        // 渐变
        gradientView.snp.makeConstraints { make in
            make.leading.trailing.equalToSuperview()
            make.bottom.equalToSuperview()
            make.top.equalToSuperview()
        }
        // 背景
        backView.snp.makeConstraints { make in
            make.leading.equalToSuperview().offset(16.0)
            make.trailing.equalToSuperview().offset(-16.0)
            make.bottom.equalToSuperview().offset(-cs_bottomInset)
            make.height.equalTo(cs_tabbarHeight-cs_bottomInset)
        }
        let padding = (CS_SCREENWIDTH - 32 * 2.0 - 16 * 2.0 - 32 * 5.0) / 4.0
        // 第一个按钮
        firstButton.snp.makeConstraints { make in
            make.leading.equalToSuperview().offset(32.0)
            make.centerY.equalToSuperview()
            make.size.equalTo(BUTTON_SIZE)
        }
        // 第二个按钮
        secondButton.snp.makeConstraints { make in
            make.leading.equalTo(firstButton.snp.trailing).offset(padding)
            make.centerY.equalToSuperview()
            make.size.equalTo(BUTTON_SIZE)
        }
        // 直播按钮
        startLiveButton.snp.makeConstraints { make in
            make.centerX.equalToSuperview()
            make.bottom.equalTo(backView)
            make.size.equalTo(CGSize(width:  72.0, height: 72.0))
        }
        // 第三个按钮
        thirdButton.snp.makeConstraints { make in
            make.trailing.equalTo(fourthButton.snp.leading).offset(-padding)
            make.centerY.equalToSuperview()
            make.size.equalTo(BUTTON_SIZE)
        }
        // 第四个按钮
        fourthButton.snp.makeConstraints { make in
            make.trailing.equalToSuperview().offset(-padding)
            make.centerY.equalToSuperview()
            make.size.equalTo(BUTTON_SIZE)
        }
        
    }

可以像开播按钮一样,直接为按钮设置普通状态下的图片或者是选中状态下的图片,也可以采用配置的形式从配置信息中获取按钮的图片信息,这并不重要,接下来我们只需要把自定义的TabBar添加到自定义UITabBarController上即可,代码如下:

    func addTabbarView() {
        let tabbarView = CSTabbarView()
        tabbarView.frame = CGRect(x: 0.0, y: CS_SCREENHIGHT - cs_tabbarHeight, width: CS_SCREENWIDTH, height: cs_tabbarHeight)
        tabbarView.delegate = self
        self.view.addSubview(tabbarView)
        self.tabbarView = tabbarView
    }

这里面的宽和高我们都可以设置为任意值,但是为了让它更贴近TabBar通常它的大小我们还是会设置的与系统UITabBar大小相同。

添加自定义动画

TabBar中的按钮原本的核心功能是用作切换UITabBarController中的子页面,但是如果可以添加一些流畅的动画无疑会提升一些用户体验,接下来我们就来实现它的点击事件并在此基础上添加一个脉冲式的动画。

首先我们需要为这些按钮添加点击事件,并用代理或者闭包的方式将点击事件传递到UITabBarController。

添加点击事件代码如下:

    func setEvent() {
        /// 第一个按钮
        firstButton.addTarget(self, action: #selector(buttonOnclick), for: .touchUpInside)
        /// 第二个按钮
        secondButton.addTarget(self, action: #selector(buttonOnclick), for: .touchUpInside)
        /// 第三个按钮
        thirdButton.addTarget(self, action: #selector(buttonOnclick), for: .touchUpInside)
        /// 第四个按钮
        fourthButton.addTarget(self, action: #selector(buttonOnclick), for: .touchUpInside)
        /// 开播按钮
        startLiveButton.addTarget(self, action: #selector(startLiveButtonTouch), for: .touchUpInside)
    }

事件实现代码如下:

    @objc func buttonOnclick(button:UIButton) {
        let index = button.tag - 100
        guard let selectedButton = selectedButton else { return }
        selectedButton.isSelected = false
        button.isSelected = true
        self.selectedButton = button
        guard let delegate = delegate else { return }
        delegate.tarBarItemTouch(index: index)
        addAnimaction(button: button)
    }
    
    // 开播
    @objc func startLiveButtonTouch() {
        addAnimaction(button: startLiveButton)
        delegate?.startLiveButtonTouch()
    }

可以看得出在这里我采用了代理的方式,将点击事件回调到UITabBarViewController,不过目前我们的关注重点应该是在动画上面,让我们来看一下动画实现,代码如下:

    // 添加动画
    func addAnimaction(button:UIButton) {
        let anim = CAKeyframeAnimation(keyPath: "transform.scale")
        anim.values = [1.0,1.1,0.9,1.0]
        anim.keyTimes = [0,0.2,0.8,1]
        anim.duration = 0.3
        button.layer.add(anim, forKey: "scale")
    }

一个非常简单的关键帧动画实现的脉冲效果,具体效果如下,(实际效果会比gif更流畅一些):

扩展功能

我们可以创建任何标签,不过最常见的还是带数量的小红点,由于它是完全自定义的,每一个按钮都是我们自己手动创建的,那么完全可以通过创建一个带标签的按钮来实现这个需求。

那么就继承自UIButton来创建一个带数量标签的按钮,具体代码如下:

class CSTabBageButton: UIButton {

    /// 角标背景
    var bageView = UIView()
    /// 角标
    var bageLabel = UILabel()
    /// 角标值
    var bageValue: Int = 0 {
        didSet {
            if bageValue <= 0 {
                bageView.isHidden = true
            } else {
                bageView.isHidden = false
                bageLabel.text = "\(bageValue)"
            }
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
        setLayout()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setupView() {
        bageView.isHidden = true
        self.addSubview(bageView)
        bageView.backgroundColor = .red
        bageView.layer.masksToBounds = true
        bageView.layer.cornerRadius = 7.5
        
        bageView.addSubview(bageLabel)
        bageLabel.textColor = .white
        bageLabel.font = UIFont.systemFont(ofSize: 10)
        bageLabel.textAlignment = .center
    }
    
    func setLayout() {
        bageView.snp.makeConstraints { make in
            make.leading.equalTo(self.snp.trailing).offset(-5.0)
            make.top.equalTo(self).offset(-5.0)
            make.height.equalTo(15.0)
        }
        bageLabel.snp.makeConstraints { make in
            make.leading.equalToSuperview().offset(5.0)
            make.width.greaterThanOrEqualTo(5.0)
            make.centerY.equalToSuperview()
            make.trailing.equalToSuperview().offset(-5.0)
        }
    }
    
    
}

然后我们用它来替换掉第三个按钮,假设我们已经收到消息,将按钮的标签数量设置为3,看一下效果,代码如下:

    /// 第三个按钮
    let thirdButton = CSTabBageButton()
    ....
    thirdButton.bageValue = 3

效果如下:

丰富交互设计

相对于系统UITabBar较为单一的交互设计,自定义TabBar的交互非常灵活,比如说中间的开播按钮,我并没有给它绑定任何属于UITabBarViewController的子视图控制器,它的点击事件我可以用来处理任何事情。

下面我们先来看一下自定义TarBar点击事件的代理,以及代理事件的实现。

代理声明代码如下:

/// 点击代理
protocol CSTabbarTouchDelegate: AnyObject {
    /// tabbar按钮点击
    func tarBarItemTouch(index:Int)
    /// 开播按钮点击
    func startLiveButtonTouch()
}

没有提供默认的实现,那么就意味着它的遵循者必须要实现这些函数,CSCustomTabBarVC中的实现如下:

    //MARK: tabbar点击代理事件
    func tarBarItemTouch(index: Int) {
        var currentIndex = index
        self.selectedIndex = currentIndex
    }
    
    /// 开播
    func startLiveButtonTouch() {
        // 检查 是否是游客登录
        if CSTouristHelper.shared.checkTouristLogin(loginSuccess: nil) {
            return
        }
        CSLiveShowManager.shared.showBroadcast()
    }

另外四个按钮我们并没有特殊干预,而是直接切换了对应的子视图控制。

而中间的开播按钮,我们自己不仅让它做了切换子控制器以外的操作,而且在操作前还进行了一些列的判断和其它操作。

可以看出我们的操作空间很大,不管是从设计,功能,还是交互,自定义UITabBar都非常灵活。

结语

通过本文的介绍和示例代码,我们探索了如何在iOS应用中自定义UITabBar,以解决系统UITabBar的各种痛点。我们不仅学会了如何改变外观、添加动画效果、还探讨了如何扩展功能和丰富交互设计。

通过继承UIView来创建自定义的TabBar,我们可以更加灵活地实现各种设计需求和交互效果,从而为用户提供更加个性化和优质的体验。希望这篇博客能为你提供有价值的指导和灵感,帮助你在iOS开发中更好地运用自定义UITabBar。

  • 35
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
iOS 的自定义 TabBar 主要分为以下几个步骤: 1. 创建自定义 TabBar 创建一个继承于 UITabBar 的类,重写初始化方法和 layoutSubviews 方法,实现自定义 TabBar 的样式和布局。 2. 实现自定义 TabBarItem 创建一个继承于 UIButton 的类,用于实现自定义TabBarItem 样式,例如添加图片、文字等。 3. 设置自定义 TabBarItem 在自定义 TabBar 的初始化方法中,添加自定义TabBarItem,将其添加到 TabBar 上。 4. 替换系统 TabBar 在 AppDelegate 中,找到 TabBarController 的 tabBar 属性,将其替换为自定义TabBar。 示例代码: 自定义 TabBar 类: ``` class CustomTabBar: UITabBar { var items: [CustomTabBarItem] = [] override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setup() { // 隐藏默认的 TabBar self.tintColor = .clear self.backgroundImage = UIImage() self.shadowImage = UIImage() self.backgroundColor = .white } override func layoutSubviews() { super.layoutSubviews() // 设置自定义 TabBarItem 的布局 let itemWidth = self.frame.size.width / CGFloat(items.count) var itemIndex: CGFloat = 0 for item in items { item.frame = CGRect(x: itemWidth * itemIndex, y: 0, width: itemWidth, height: self.frame.size.height) itemIndex += 1 } } } ``` 自定义 TabBarItem 类: ``` class CustomTabBarItem: UIButton { var title: String? var normalImage: UIImage? var selectedImage: UIImage? override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setup() { // 设置 TabBarItem 的样式 self.imageView?.contentMode = .scaleAspectFit self.titleLabel?.font = UIFont.systemFont(ofSize: 12) self.setTitleColor(.gray, for: .normal) self.setTitleColor(.black, for: .selected) } override func layoutSubviews() { super.layoutSubviews() // 设置 TabBarItem 的布局 let imageHeight = self.frame.size.height * 0.6 self.imageView?.frame = CGRect(x: (self.frame.size.width - imageHeight) / 2, y: 5, width: imageHeight, height: imageHeight) self.titleLabel?.frame = CGRect(x: 0, y: self.frame.size.height - 20, width: self.frame.size.width, height: 20) } func set(title: String?, normalImage: UIImage?, selectedImage: UIImage?) { self.title = title self.normalImage = normalImage self.selectedImage = selectedImage // 设置 TabBarItem 的标题和图片 self.setTitle(title, for: .normal) self.setImage(normalImage, for: .normal) self.setImage(selectedImage, for: .selected) } } ``` 在自定义 TabBar 的初始化方法中,添加自定义TabBarItem: ``` class CustomTabBar: UITabBar { var items: [CustomTabBarItem] = [] override init(frame: CGRect) { super.init(frame: frame) setup() // 添加 TabBarItem let item1 = CustomTabBarItem() item1.set(title: "首页", normalImage: UIImage(named: "home_normal"), selectedImage: UIImage(named: "home_selected")) self.addSubview(item1) items.append(item1) let item2 = CustomTabBarItem() item2.set(title: "消息", normalImage: UIImage(named: "message_normal"), selectedImage: UIImage(named: "message_selected")) self.addSubview(item2) items.append(item2) let item3 = CustomTabBarItem() item3.set(title: "我的", normalImage: UIImage(named: "mine_normal"), selectedImage: UIImage(named: "mine_selected")) self.addSubview(item3) items.append(item3) } // ... } ``` 在 AppDelegate 中,找到 TabBarController 的 tabBar 属性,将其替换为自定义TabBar: ``` func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // ... let tabBarController = UITabBarController() tabBarController.viewControllers = [viewController1, viewController2, viewController3] // 替换为自定义 TabBar let customTabBar = CustomTabBar(frame: tabBarController.tabBar.frame) tabBarController.setValue(customTabBar, forKey: "tabBar") // ... return true } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值