艺术成分很高的完全自定义的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 = .whitehiddenRealTabbar()}//MARK: 隐藏tabbarprivate func hiddenRealTabbar() {for view in view.subviews {if view.isKind(of: UITabBar.self) {view.isHidden = truebreak}}}

完全自定义外观

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

class CSTabbarView: UIView {/// 当前选中buttonvar 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 = .whitebackView.layer.cornerRadius = (cs_tabbarHeight-cs_bottomInset)/2.0backView.layer.shadowOffset = CGSize(width: 0, height: 3)backView.layer.shadowColor = UIColor.black.withAlphaComponent(0.1).cgColorbackView.layer.shadowOpacity = 1backView.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 inmake.leading.trailing.equalToSuperview()make.bottom.equalToSuperview()make.top.equalToSuperview()}// 背景backView.snp.makeConstraints { make inmake.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 inmake.leading.equalToSuperview().offset(32.0)make.centerY.equalToSuperview()make.size.equalTo(BUTTON_SIZE)}// 第二个按钮secondButton.snp.makeConstraints { make inmake.leading.equalTo(firstButton.snp.trailing).offset(padding)make.centerY.equalToSuperview()make.size.equalTo(BUTTON_SIZE)}// 直播按钮startLiveButton.snp.makeConstraints { make inmake.centerX.equalToSuperview()make.bottom.equalTo(backView)make.size.equalTo(CGSize(width:  72.0, height: 72.0))}// 第三个按钮thirdButton.snp.makeConstraints { make inmake.trailing.equalTo(fourthButton.snp.leading).offset(-padding)make.centerY.equalToSuperview()make.size.equalTo(BUTTON_SIZE)}// 第四个按钮fourthButton.snp.makeConstraints { make inmake.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 = selfself.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 - 100guard let selectedButton = selectedButton else { return }selectedButton.isSelected = falsebutton.isSelected = trueself.selectedButton = buttonguard 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.3button.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 = falsebageLabel.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 = trueself.addSubview(bageView)bageView.backgroundColor = .redbageView.layer.masksToBounds = truebageView.layer.cornerRadius = 7.5bageView.addSubview(bageLabel)bageLabel.textColor = .whitebageLabel.font = UIFont.systemFont(ofSize: 10)bageLabel.textAlignment = .center}func setLayout() {bageView.snp.makeConstraints { make inmake.leading.equalTo(self.snp.trailing).offset(-5.0)make.top.equalTo(self).offset(-5.0)make.height.equalTo(15.0)}bageLabel.snp.makeConstraints { make inmake.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 = indexself.selectedIndex = currentIndex}/// 开播func startLiveButtonTouch() {// 检查 是否是游客登录if CSTouristHelper.shared.checkTouristLogin(loginSuccess: nil) {return}CSLiveShowManager.shared.showBroadcast()}

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

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

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

结语

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

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

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

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

相关文章

显卡驱动程序下载失败的原因及对策

在数字时代&#xff0c;显卡作为电脑的心脏部件之一&#xff0c;其驱动程序的正常运行是保证图形处理性能的关键。然而&#xff0c;不少用户在尝试下载显卡驱动程序时遭遇失败&#xff0c;这不仅影响了日常使用体验&#xff0c;还可能埋下系统不稳定的风险。本文将深入探讨显卡…

黑龙江网络安全等级保护测评策略概述

一、简介 黑龙江省网络安全等级保护测评策略是为了保障信息系统安全稳定运行&#xff0c;根据《网络安全法》和相关国家标准制定的综合性安全评估和加固过程。该策略不仅要求企业和机构明确自身信息系统的安全等级&#xff0c;还指导其实施相应的技术防护与管理措施&#xff0…

算法学习4——动态规划

动态规划&#xff08;Dynamic Programming&#xff0c;简称DP&#xff09;是一种用于解决具有重叠子问题和最优子结构性质的问题的算法设计技术。它通过将复杂问题分解为更小的子问题&#xff0c;并保存子问题的解来避免重复计算&#xff0c;从而提高算法的效率。 基本思想 动…

Mamba中的Mamba:在标记化Mamba模型中的集中式Mamba跨扫描高光谱图像分类

摘要 https://arxiv.org/pdf/2405.12003 高光谱图像&#xff08;HSI&#xff09;分类在遥感&#xff08;RS&#xff09;领域至关重要&#xff0c;尤其是随着深度学习技术的不断进步。顺序模型&#xff0c;如循环神经网络&#xff08;RNNs&#xff09;和Transformer&#xff0…

接近50个实用编程相关学习资源网站

Date: 2024.07.17 09:45:10 author: lijianzhan 编程语言以及编程相关工具等实用性官方文档网站 C语言文档&#xff1a;https://learn.microsoft.com/zh-cn/cpp/c-languageMicrosoft C、C和汇编程序文档&#xff1a;https://learn.microsoft.com/zh-cn/cppJAVA官方文档&#…

java题目之数字加密以及如何解密

public class Main6 {public static void main(String[] args) {// 某系统的数字密码&#xff08;大于0&#xff09;&#xff0c;比如1983&#xff0c;采用加密方式进行传输//定义了一个静态数组int []arr{1,9,8,3};//1.加密//先给每位数加上5for (int i 0; i <arr.length …

随机变量的数学期望

目录 简介 基本概念 数学期望的定义 数学期望的性质 数学期望的应用 计算实例 数学期望在解决哪些具体问题时最为有效&#xff1f; 如何计算两个或多个随机变量的组合概率及其期望值&#xff1f; 1. 计算组合概率 2. 计算期望值 当涉及到两个或多个随机变量的组合时&…

Hadoop基础组件介绍!

Hadoop是一个由Apache基金会所开发的分布式系统基础架构&#xff0c;Hadoop生态系统已经远远超出了这些基本组件&#xff0c;现在包括了多种组件和技术&#xff0c;详情介绍如下&#xff1a; HDFS&#xff08;Hadoop Distributed File System&#xff09; HDFS是Hadoop的核心组…

git实操之线上分支合并

线上分支合并 【 1 】本地dev分支合并到本地master上 # 本地dev分支合并到本地master上# 远程(线上)分支合并# 本地dev分支合并到本地master上# 远程(线上)分支合并#####本地和线上分支同步################ #### 远程创建分支&#xff0c;拉取到本地####-远程创建分支&#…

自定义Bean转换工具类

BeanConvertor工具类&#xff1a;简化Java对象转换的利器 在Java开发中,我们经常需要在不同的对象之间转换数据。这可能是因为我们需要将数据从一个层(如数据访问层)转移到另一个层(如服务层或表示层),或者是因为我们需要将外部API的数据结构转换为我们的内部数据结构。这种转…

企业级-PDF图片水印

作者&#xff1a;fyupeng 技术专栏&#xff1a;☞ https://github.com/fyupeng 项目地址&#xff1a;☞ https://github.com/fyupeng/distributed-blog-system-api 留给读者 遇到签名&#xff0c;往往很无奈签名的位置、大小。 一、介绍 直接提供PDF路径和图片路径&#xff0…

RK RGA _MMU unsupported memory larger then 4G!问题解决

使用RGA程序,长时间运行的过程中出现了rga_mm: RGA_MMU unsupported memory larger than 4G! rga_mm: RGA_MMU unsupported memory larger than 4G! rga_mm: scheduler core[4] unsupported mm_flag[0x8]! rga_mm: rga_mm_map_buffer map virtual address error! rga_mm: job…

华为云.云日志服务LTS及其基本使用

云计算 云日志服务LTS及其基本使用 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/qq_28550…

2024最新版虚拟便携空调小程序源码 支持流量主切换空调型号

产品截图 部分源代码展示 urls.js Object.defineProperty(exports, "__esModule", {value: !0 }), exports.default ["9c5f1fa582bee88300ffb7e28dce8b68_3188_128_128.png", "E-116154b04e91de689fb1c4ae99266dff_960.svg", "573eee719…

酱酒七个轮次口感与特点,哪个轮次最好喝?

都知道酱香型白酒是按照“12987”工艺酿造而成,这12987便是以一整年为一个生产周期,中间经历润沙下沙,并在多次的蒸煮发酵后,完成七次取酒。 所以酱香型白酒是由7个轮次的基酒勾调而成的,这七轮次酒口感特点各不相同,品质也是有着极大的差异。而这各个轮次基酒的勾调配比又直接…

【踩坑日记26】Connection timed out fatal: expected flush after ref listing ```

问题描述 (base) XXXomega:/home/XXX/code$ git clone https://github.com/comeeasy/DALS.git Cloning into DALS... error: RPC failed; curl 28 Failed to connect to github.com port 443: Connection timed out fatal: expected flush after ref listing解决方法 直接换一…

mysql的索引、事务和存储引擎

目录 索引 索引的概念 索引的作用 作用 索引的副作用 创建索引 创建索引的原则和依据 索引的类型 创建索引 查看索引 删除索引 drop 主键索引 普通索引 添加普通索引 唯一索引 添加唯一索引 组合索引 添加组合索引 查询组合索引 全文索引 添加全文索引 …

构建高效Node.js中间层:探索请求合并转发的艺术

&#x1f389; 博客主页&#xff1a;【剑九 六千里-CSDN博客】 &#x1f3a8; 上一篇文章&#xff1a;【CSS盒模型&#xff1a;掌握网页布局的核心】 &#x1f3a0; 系列专栏&#xff1a;【面试题-八股系列】 &#x1f496; 感谢大家点赞&#x1f44d;收藏⭐评论✍ 引言&#x…

深度学习简介(框架)

目录 1. 深度学习简介1.1 历史背景1.2 核心原理1.3 应用领域1.4 技术挑战1.5 未来趋势 2. 深度学习与传统机器学习2.1 深度学习的原理2.2 深度学习的优势2.3 深度学习的应用2.4 深度学习的挑战2.5 深度学习的未来 3. 深度学习的关键技术3.1 神经网络基础3.2 卷积神经网络&#…

java中的常量池

类文件常量池 类文件结构概览 一个Java类文件的大致结构如下&#xff1a; 魔数&#xff08;Magic Number&#xff09;&#xff1a;标识文件类型&#xff0c;值为0xCAFEBABE。 版本号&#xff08;Version&#xff09;&#xff1a;次版本号和主版本号。 常量池&#xff08;Cons…