艺术成分很高的完全自定义的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;还可能埋下系统不稳定的风险。本文将深入探讨显卡…

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

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

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. 计算期望值 当涉及到两个或多个随机变量的组合时&…

git实操之线上分支合并

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

自定义Bean转换工具类

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

华为云.云日志服务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…

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

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

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

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

接口测试JMeter-1.接口测试初识

第一章 接口测试初识 1. 接口测试理论基础 “接口测试”一个让人觉得非常高大上的名词&#xff0c;特别是对于刚入门的测试同学而言。随着测试技术不断的深化&#xff0c;“接口测试”出现在我们视野中的频次越来越高。那么接口测试到底是如何做的&#xff1f;接口测试的优势又…

Flowable-SpringBoot项目集成

在前面的介绍中&#xff0c;虽然实现了绘制流程图&#xff0c;然后将流程图存储到数据库中&#xff0c;然后从数据库中获取流程信息&#xff0c;并部署和启动流程&#xff0c;但是部署的流程绘制器是在tomcat中部署的&#xff0c;可能在部分的项目中&#xff0c;需要我们将流程…

<数据集>pcb板缺陷检测数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;693张 标注数量(xml文件个数)&#xff1a;693 标注数量(txt文件个数)&#xff1a;693 标注类别数&#xff1a;6 标注类别名称&#xff1a;[missing_hole, mouse_bite, open_circuit, short, spurious_copper, spur…

git 提交的进阶操作

cherry-pick cherry-pick 是 Git 中的一种操作,允许你从一个分支中选择特定的 commit,并将其应用到另一个分支。它的主要用途是将特定的更改引入到其他分支,而无需合并整个分支历史。这在修复 bug 或者移植某些功能时特别有用。 cherry-pick 的使用场景 Bug 修复: 例如,你…

Python面试宝典第16题:跳跃游戏

题目 给你一个非负整数数组 nums &#xff0c;你最初位于数组的第一个下标 &#xff0c;数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true。否则&#xff0c;返回 false。 示例 1&#xff1a; 输…

detection_segmentation

目标检测和实例分割(OBJECT_DETECTION AND INSTANCE SEGMENTATION) 文章目录 目标检测和实例分割(OBJECT_DETECTION AND INSTANCE SEGMENTATION)一. 计算机视觉(AI VISION)1. 图像分类2. 目标检测与定位3. 语义分割和实例分割目标检测算法可以分为两大类&#xff1a; R-CNN生成…

Linux系统:揭开它神秘面纱的科普之旅

在这个数字化时代&#xff0c;电脑和手机成了我们生活中不可或缺的一部分。而提到这些设备的操作系统&#xff0c;大家可能首先想到的是Windows、macOS或是Android。 但你知道吗&#xff0c;在技术的海洋里&#xff0c;还有一个强大而灵活的操作系统家族&#xff0c;它就是Lin…

python-多任务编程

2. 多任务编程 2.1 多任务概述 多任务 即操作系统中可以同时运行多个任务。比如我们可以同时挂着qq&#xff0c;听音乐&#xff0c;同时上网浏览网页。这是我们看得到的任务&#xff0c;在系统中还有很多系统任务在执行,现在的操作系统基本都是多任务操作系统&#xff0c;具备…

JVM--HostSpot算法细节实现

1.根节点枚举 定义&#xff1a; 我们以可达性分析算法中从GC Roots 集合找引用链这个操作作为介绍虚拟机高效实现的第一个例 子。固定可作为GC Roots 的节点主要在全局性的引用&#xff08;例如常量或类静态属性&#xff09;与执行上下文&#xff08;例如 栈帧中的本地变量表&a…

时间序列预测方法概述

这里写目录标题 时间序列预测方法概述1.统计方法1.1 ARIMA (AutoRegressive Integrated Moving Average)1.2 State Space Models1.3 Exponential Smoothing 2.机器学习方法2.1 SVM (Support Vector Machines)2.2 RF (Random Forest)2.3 KNN (K-Nearest Neighbors) 3. 深度学习方…