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

news2024/11/25 11:06:33

引言

在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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1941727.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ai绘画变现的14种途径 学习Stablediffusion midjourney用途

AIGC&#xff0c;一个在当代社会中不可忽视的词汇&#xff0c;指的是利用人工智能技术生成创作内容。近年来&#xff0c;全球范围内涌现出50个热门的AI工具&#xff0c;其中&#xff0c;以140亿次访问量雄踞榜首的“GBT”&#xff0c;无疑是AI领域的领头羊。在这些工具中&#…

node.js中nodemon : 无法加载和使用问题,这是由于windows安全策略影起的按如下操作即可

1、用管理员权限打开vscode 2、文件终端中打开&#xff0c;输入 Set-ExecutionPolicy -Scope CurrentUser 3、再输入RemoteSigned 4、使用get-ExecutionPolicy查看权限&#xff0c;可以看到变为了RemoteSigned 重启问题解决

一个注解解决重复提交问题

一、前言 ​ 在应用系统中提交是一个极为常见的功能&#xff0c;倘若不加管控&#xff0c;极易由于用户的误操作或网络延迟致使同一请求被发送多次&#xff0c;从而生成重复的数据记录。针对用户的误操作&#xff0c;前端通常会实现按钮的 loading 状态&#xff0c;以阻…

【leetcode】排列序列

给出集合 [1,2,3,...,n]&#xff0c;其所有元素共有 n! 种排列。 按大小顺序列出所有排列情况&#xff0c;并一一标记&#xff0c;当 n 3 时, 所有排列如下&#xff1a; "123""132""213""231""312""321" 给定…

构建高效园区导览系统:基于3DGIS与物联网技术的实现方案

园区导航的挑战与机遇 在现代化的大型园区中&#xff0c;随着面积的不断扩张和布局的日益复杂&#xff0c;传统的纸质地图已难以满足日益增长的导航需求。每栋楼、每层楼都有着不同的办公室&#xff0c;不同的业务。这种低效的寻路过程不仅影响了客户的来访体验&#xff0c;也…

介绍一下TCP/IP 模型和 OSI 模型的区别

OSI 模型是由国际标准化组织制定的一个用于计算机或通信系统间互联的标准体系&#xff0c;一共有七层&#xff0c;由上而下分别为应用层&#xff0c;表示层&#xff0c;会话层&#xff0c;传输层&#xff0c;网络层&#xff0c;数据链路层和物理层&#xff0c;虽然 OSI 模型理论…

系统架构设计师教程 第4章 信息安全技术基础知识-4.1 信息安全基础知识-解读

系统架构设计师教程 第4章 信息安全技术基础知识-4.1 信息安全基础知识 4.1.1 信息安全的概念4.1.1.1 信息安全的范围4.1.1.1.1 设备安全4.1.1.1.2 数据安全4.1.1.1.3 内容安全4.1.1.1.4 行为安全 4.1.2 信息存储安全4.1.2.1 信息使用的安全4.1.2.1.1 用户的标识与验证4.1.2.1.…

免费【2024】springboot 趵突泉景区的智慧导游小程序

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

监控电脑进程,避免程序在打开前就已经在运行

文章目录 一、文章的目的&#xff08;适用于windows&#xff09;二、处理方式三、进程查看的内容在窗口端的演示四、附上代码例子四、通过os.kill的方式&#xff0c;再回到原来的表格时&#xff0c;会出现如下错误提示&#xff1a; 一、文章的目的&#xff08;适用于windows&am…

Flink笔记整理(三)

Flink笔记整理&#xff08;三&#xff09; 文章目录 Flink笔记整理&#xff08;三&#xff09;五、DataStream API5.1Environment5.2 Source5.3 Transformation5.4 Sink 总结 五、DataStream API DataStream API是Flink的核心层API&#xff0c;一个Flink程序&#xff0c;其实本…

谷粒商城实战笔记-44-前端基础-Vue-整合ElementUI快速开发/设置模板代码

文章目录 一&#xff0c;安装导入ElementUI1&#xff0c;安装 element-ui2&#xff0c;导入 element-ui 二&#xff0c;ElementUI 实战1&#xff0c;将 App.vue 改为 element-ui 中的后台布局2&#xff0c;开发导航栏2.1 开发MyTable组件2.2 注册路由2.3 改造App.vue2.4 新增左…

传统行业 IT 团队数字化转型该如何进行?

一、前言 数字化转型概念&#xff1a; 数字化转型是“以更加数字化的方式重新思考&#xff0c;重新塑造商业。是从根本上考虑交付渠道&#xff0c;运营&#xff0c;市场和销售以及顾客关注——所有的业务模块&#xff0c;并重新思考这些能否包装成新的数字化产品和服务&#…

基于微信小程序+SpringBoot+Vue的青少年科普教学系统平台(带1w+文档)

基于微信小程序SpringBootVue的青少年科普教学系统平台(带1w文档) 基于微信小程序SpringBootVue的青少年科普教学系统平台(带1w文档) 这个工具就是解决上述问题的最好的解决方案。它不仅可以实时完成信息处理&#xff0c;还缩短高校教师成果信息管理流程&#xff0c;使其系统化…

挖掘基于边缘无线协同感知的低功耗物联网 (LPIOT) 的巨大潜力

关键词&#xff1a;边缘无线协同感知、低功耗物联网(LPIOT)、无线混合组网、用电监测、用电计量、多角色、计量插座、无线场景感知、多角色运用、后台边缘层&#xff0c;网络边缘层&#xff0c;场景能效管理&#xff0c;场景能耗计算 在数字化和智能化日益加速的今天&#xff…

Elasticsearch:Java ECS 日志记录 - log4j2

ECS 记录器是你最喜欢的日志库的格式化程序/编码器插件。它们可让你轻松将日志格式化为与 ECS 兼容的 JSON。ECS 兼容的 JSON 日志记录可以帮我们简化很多分析&#xff0c;可视化及解析的工作。在今天的文章里&#xff0c;我来详述如何在 Java 应用里生成 ECS 相兼容的日志。 …

路由器ip地址脱机是什么意思?怎么应对

在数字化时代&#xff0c;路由器作为家庭或企业网络连接的核心设备&#xff0c;其稳定性和连通性对于我们的网络体验至关重要。然而&#xff0c;有时我们可能会遇到路由器IP地址显示脱机的情况&#xff0c;这不仅影响了我们的网络访问&#xff0c;还可能对工作和娱乐造成不便。…

嵌入式C/C++、FreeRTOS、STM32F407VGT6和TCP:智能家居安防系统的全流程介绍(代码示例)

1. 项目概述 随着物联网技术的快速发展,智能家居安防系统越来越受到人们的重视。本文介绍了一种基于STM32单片机的嵌入式安防中控系统的设计与实现方案。该系统集成了多种传感器,实现了实时监控、报警和远程控制等功能,为用户提供了一个安全、可靠的家居安防解决方案。 1.1 系…

自动驾驶---视觉Transformer的应用

1 背景 在过去的几年&#xff0c;随着自动驾驶技术的不断发展&#xff0c;神经网络逐渐进入人们的视野。Transformer的应用也越来越广泛&#xff0c;逐步走向自动驾驶技术的前沿。笔者也在博客《人工智能---什么是Transformer?》中大概介绍了Transformer的一些内容&#xff1a…

Unity DOTS中的world

Unity DOTS中的world 注册销毁逻辑自定义创建逻辑创建world创建system group插入player loopReference DOTS中&#xff0c;world是一组entity的集合。entity的ID在其自身的世界中是唯一的。每个world都拥有一个EntityManager&#xff0c;可以用它来创建、销毁和修改world中的en…

ssh到huawei交换机

import re from netmiko import ConnectHandlerips [ 10.32.5.130, 10.32.5.131, 10.32.5.132, 10.32.5.133, 10.32.5.134, 10.32.5.135, ]for ip in ips:hw_fw {device_type: huawei,host: ip, username: 493031,password: 密码,port: 22 }net_connect ConnectHandler(*…