Swift 如何实现自定义 Tab Bar

news2024/11/19 20:38:47

前言

每个 UI 设计师都喜欢美丽而有动画效果的 Tab Bar。然而,对于开发人员来说,实现这种设计可能是一场噩梦。当然,使用 Apple 的原生 Tab Bar 组件并专注于更有趣的事情,比如业务逻辑的实现,会更容易。但如果我们必须创建自定义 Tab Bar 该如何实现呢?

介绍实现流程

在本文中,将尝试回答这些问题。我们将介绍创建自定义 Tab Bar 的最重要方面。最终效果将是一个具有动画效果、易于扩展、完全自定义的 Tab Bar,希望它能为你节省将来的时间,使设计师梦寐以求的 Tab Bar 的实现更快捷和更舒适。下面来介绍一下实现流程:

首先,需要说明的是,本示例使用了一些依赖项。借助这些依赖项,更容易实现本文中描述的解决方案。随时将它们替换为你的本机代码或其他库。这些依赖关系包括:

  • 用于布局的 SnapKit
  • 用于处理 Tab Bar 项上的点击操作的 RxGesture
  • 用于通知 TabBarController 选中项目的 RxSwift

现在,让我们来实现这个功能。

Tab Bar 初步介绍

Tab Bar 项组件由两个不同的部分组成:视图层和处理所有定义的项目类型的枚举。

为什么使用枚举类型而不是简单的结构?

这很简单,枚举在编程中特别是在 Swift 语言中是强大的类型。它们将熟悉的情况汇总在一起,可迭代,可以扩展以展开有关特定情况的信息,或者可以在其中使用计算属性。

此外,枚举与结构一样是值类型。

enum CustomTabItem: String, CaseIterable {
    case profile
}

extension CustomTabItem {
    var viewController: UIViewController { }
    var icon: UIImage? { }
    var selectedIcon: UIImage? { }
    var name: String { }
}

根据上面的代码可以看出,CustomTabItem 具有用于图标、选定图标、名称和关联 viewController 的属性。每个值都使用 switch 语句定义,为每个枚举情况返回特定值。

第二部分是 Tab Bar 项的视图层。它直接与 CustomTabItem 关联,因为 Tab Bar 项是初始化期间传递给视图的两个参数之一。第二个参数是每个视图唯一的索引,稍后将用于更改 Tab Bar 的 selectedIndex

此外,每个视图都具有简单的过渡动画。项目的设计很简单,包括顶部的图标和下面的标题。根据项目是否已选择,标题可以变成图标下面的圆线。

Tab Bar实现

让我们聊聊如何将所有 Tab Bar 项汇总并处理其选择的主要组件。自动布局非常容易,因为它是 UIStackView 组件的子类。唯一需要做的事情就是使用 addArrangedSubview(_ view: UIView) 方法添加我们的项目。这是在 setupHierarchy() 方法中处理的。

GitHub 存储库的 Extensions.swift 文件中,可以找到一些有用的扩展,比如用一行代码向 UIStackView 添加许多子视图的扩展。

此外,对于要添加到 Tab Bar 的每个项目,我们必须将 translatesAutoresizingMaskIntoConstraints 设置为 false,以防止自动创建它们的自动布局,并将 clipsToBounds 设置为 true,以将我们的项目剪切到 Tab Bar 的边界。我们将在 setupProperties() 方法中实现这一部分,以及其他属性配置。

final class CustomTabBar: UIStackView {
    
    var itemTapped: Observable<Int> { itemTappedSubject.asObservable() }
    
    private let itemTappedSubject = PublishSubject<Int>()
    private let disposeBag = DisposeBag()
    
    init() { }
    
    required init(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

除了Tab Bar项的声明之外,我们还必须添加负责发出所选项目索引的 Observable 序列属性。

正如上面所示,主体本身声明为私有常量,并且具有关于主题的值的 Observable。

由于这样做,我们可以确保没有人能够干扰从 CustomTabBar 类外部发出的该主题的值。

最后但同样重要的是,我们必须添加两个方法到我们的 CustomTabBar 实现中。第一个是 selectItem(index: Int),在这里,我们将处理更新当前所选项目的所有逻辑。

首先,我们更新每个项目中的 isSelected 属性,以反映最新的选择。

其次,我们使用上面描述的主题发出所选项目的索引。通过这种方式,我们同时在 TabBarController 和 Tab Bar 项中更新选择。核心代码如下:

private func selectItem(index: Int) {
    customItemViews.forEach { $0.isSelected = $0.index == index }
    itemTappedSubject.onNext(index)
}

我们必须实现的第二个方法是 bind(),负责处理用户对我们的每个项目的触摸。

实现使用 RxGesture,但如果你愿意,可以用你自己的 Reactive 扩展替换它。RxGesture 提供了一种 .tapGesture() 方法,用于识别用户交互,但在绑定到该操作之前,我们必须过滤用户手势,仅选择具有已识别状态的手势。核心代码如下:

private func bind() {
    profileItem.rx.tapGesture()
        .when(.recognized)
        .bind { [weak self] _ in
            guard let self = self else { return }
            self.profileItem.animateClick {
                self.selectItem(index: self.profileItem.index)
            }
        }
        .disposed(by: disposeBag)
}

如上面代码,animateClick(completion: @escaping () -> Void) 的闭包内部调用了 selectItem(index: Int) 方法。这是一个 UIView 的扩展,用于缩放动画我们的项目。

上面的代码示例介绍了如何处理 profileItem 的用户交互。但不要忘记以相同的方式处理其他相关的组件!

布局在 Tab Bar Controller 中

到目前为止,前期工作都已完成,现在我们必须将所有部分组合在一起!我们可以使用 CustomTabBarController 来完成,但首先,我们需要一个用于处理与 Tab Bar 项目相关的不同屏幕的简单视图控制器。代码如下:

class ViewController: UIViewController {
    
    private let titleLabel = UILabel()
    private let item: CustomTabItem
    
    init(item: CustomTabItem) {
        self.item = item
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

上面的示例故意省略了自动布局和属性配置,因为这是一个简单的添加。每个视图控制器都使用 CustomTabItem 进行初始化,因此我们可以在屏幕上显示项目的名称。

回到 Tab Bar 控制器,除了层次结构和布局设置之外,我们必须配置一些属性。代码如下:

private func setupProperties() {
    tabBar.isHidden = true
        
    customTabBar.translatesAutoresizingMaskIntoConstraints = false
    customTabBar.addShadow()
        
    selectedIndex = 0
    let controllers = CustomTabItem.allCases.map { $0.viewController }
    setViewControllers(controllers, animated: true)
}

首先,隐藏可以通过名为 tabBar 的类属性访问的本机 Tab Bar。

其次,我们将 translatesAutoresizingMaskIntoConstraints 设置为 false,并向自定义 TabBar 组件添加一些阴影。

最后,我们必须将 Tab Bar ControllerselectedIndex 属性设置为起始值(最常见的情况是将该值设置为 0),并设置将由 Tab Bar Controller 处理的视图控制器。

由于我们的 Tab Bar Item 实现具有与每个枚举映射到其各自值的 View Controller 相关联的属性,而且它是 CaseIterable,因此我们可以轻松地将我们的枚举的所有情况映射到它们各自的 View Controller 值。映射后,它们将使用:

setViewControllers(_ controllers: [UIViewController], animated: true)

现在只剩下一步,我们必须处理当前 selectedIndex 的更改。

如果你在之前有所关注,那么你将立即知道如何实现这一点。当然是通过 CustomTabBar 类中以前创建的 Observable 序列,从而实现这个功能。

对于从 CustomTabBar 类外部发出的每个索引值,我们选择特定的选项卡,通过 selectTabWith(index: Int) 方法将作为参数传递的索引分配给Tab Bar Controller 的 selectedIndex 属性。

自定义Tab Bar实现的最终结果

至此,我们就完成了自定义Tab Bar的实现!

总结

总的来说,这篇文章详细介绍了如何创建一个自定义的 Tab Bar,为移动应用的 UI 设计增添了美感和交互性。我们从枚举类型的优势开始,解释了为什么使用枚举来定义 Tab Bar 的各个项目。通过对 Tab Bar 项的视图层的设计和动画效果,我们使 Tab Bar 更加生动和吸引人。

文章还深入讨论了自定义 Tab Bar 的实现,包括处理用户交互、布局和在 Tab Bar Controller 中的配置。我们使用了 RxSwift 等工具来处理项目选择,并展示了如何将所有这些部分组合在一起,以实现一个完全自定义的 Tab Bar。

最终,这个自定义 Tab Bar 为 UI 设计师和开发人员提供了一个更灵活的选择,使得移动应用的界面更加吸引人。希望本文的内容对于那些希望提升用户体验的开发人员和设计师来说是有益的,能够帮助他们更好地实现他们的创意和设计理念。

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

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

相关文章

CPU、MCU、MPU、DSP、FPGA各是什么?有什么区别?

1、CPU 中央处理器&#xff0c;简称 CPU&#xff08;Central Processing Unit&#xff09;&#xff0c;中央处理器主要包括两个部分&#xff0c;即控制器、运算器&#xff0c;其中还包括高速缓冲存储器及实现它们之间联系的数据、控制的总线。 电子计算机三大核心部件就是CPU…

了解c++11中的新增

一&#xff0c;统一的初始化列表 在引入c11后&#xff0c;我们得出计划都可以用初始化列表进行初始化。 C11 扩大了用大括号括起的列表 ( 初始化列表 ) 的使用范围&#xff0c;使其可用于所有的内置类型和用户自 定义的类型&#xff0c; 使用初始化列表时&#xff0c;可添加等…

JS基础之原型原型链

JS基础之原型&原型链 原型&原型链构造函数创建对象prototypeprotoconstructor实例与原型原型的原型原型链其他constructorproto继承 原型&原型链 构造函数创建对象 我们先使用构造函数创建一个对象&#xff1a; function Person(){ } var person new Person();…

【数据结构和算法】到达首都的最少油耗

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 三、代码 四、复杂度分析 前言 这是力扣的2477题&#xff0c;难度为中等&#xff0c;解题方案有很多种&…

tomcat源码学习记录

tomcat 学习记录 tomcat 编译ant 下载编译运行 源码Debug运行 Bootstrap运行Tomcat查看状态 pom.xml测试EmbeddedTomcat 参考书籍博客 tomcat 编译 下载 tomcat 10 源码&#xff0c;解压然后idea导入 包存放的默认位置如下&#xff1a;base.path${user.home}/tomcat-build-lib…

Linux升级nginx版本

处于漏洞修复目的服务器所用nginx是1.16.0版本扫出来存在安全隐患&#xff0c;需要我们升级到1.17.7以上。 一般nginx默认在 /usr/local/ 目录&#xff0c;这里我的nginx是自定义的路径安装在 /app/weblogic/nginx 。 1.查看生产环境nginx版本 cd /app/weblogic/nginx/sbin/…

class065 A星、Floyd、Bellman-Ford与SPFA【算法】

class065 A星、Floyd、Bellman-Ford与SPFA【算法】 2023-12-9 19:27:02 算法讲解065【必备】A星、Floyd、Bellman-Ford与SPFA code1 A*算法模版 // A*算法模版&#xff08;对数器验证&#xff09; package class065;import java.util.PriorityQueue;// A*算法模版&#xff…

Elon Musk艾隆・马斯克的聊天机器人Grok上线可以使用啦,为X Premium Plus订阅者推出

艾隆・马斯克旗下的 AI 初创公司X&#xff08;前身“推特”&#xff09;开发的 ChatGPT 竞争对手 Grok 已经在 X 平台上正式推出。Grok 是一个基于生成模型 Grok-1的聊天机器人&#xff0c;它能够回答问题并提供最新的信息。与其他聊天机器人不同&#xff0c;Grok 可以实时获取…

luceda ipkiss教程 44:在PyCharm 中设置Template text

通过设置Template text&#xff0c;可以提升写代码的速度和版图设计效率。 设置了Template text&#xff0c;在PyCharm 命令窗口输入i3后按enter建&#xff0c;就可以快速输入 from ipkiss3 import all as i3 这一段代码&#xff0c;使用起来也是非常方便&#xff1a; 设置过程…

万界星空科技mes系统中看板管理

我们很多企业现在都有大屏&#xff0c;那到底万界星空科技低代码云mes系统管理中看板管理有什么作用&#xff1f;我总结了几条: 1.提高车间的生产效率 2.有效的监控设备运行状况 3.控制生产线运行 4.增加和改善用户体验 5.提高工作效率和工作安全性

课堂练习3.4:进程的切换

3-9 课堂练习3.4:进程的切换 进程切换是支持多进程的一个关键环节,涉及到 CPU 现场的保存和恢复,本实训分析 Linux 0.11 的进程切换过程。 第1关第一次进程切换过程分析 任务描述 本关任务回答问题: 在第一次进程切换时: 1.是从几号进程切换到几号进程?0 号进程和 1 号…

人工智能大型语言模型的突破

近年来&#xff0c;随着深度学习和人工智能技术的飞速发展&#xff0c;大型语言模型在自然语言处理领域取得了巨大的突破&#xff0c;引发了广泛的关注和讨论。本文将介绍大型语言模型的发展历程、技术原理、应用场景以及未来发展趋势。 一、发展历程大型语言模型的发展可以追…

Android : Xui- RecyclerView+BannerLayout 轮播图简单应用

实例图&#xff1a; 1.引用XUI http://t.csdnimg.cn/Wb4KR 2.创建显示图片布局 banner_item.xml <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"…

综述 2017-Genome Biology:Alignment-free sequence comparison

Zielezinski, Andrzej, et al. "Alignment-free sequence comparison: benefits, applications, and tools." Genome biology 18 (2017): 1-17. https://genomebiology.biomedcentral.com/articles/10.1186/s13059-017-1319-7 被引次数&#xff1a;476应用问题&…

【九】spring、springmvc、springboot、springcloud

spring、springmvc 、springboot 、springcloud 简介 从事IT这么些年&#xff0c;经历了行业技术的更迭&#xff0c;各行各业都会有事务更新&#xff0c;IT行业技术更迭速度快的特点尤为突出&#xff0c;或许这也是从事这个行业的压力所在&#xff0c;但另一方面反应了这个行业…

利用jdbc对数据库进行增删改查

步骤/过程&#xff1a; 1&#xff0c;导入驱动包 2&#xff0c;加载驱动包 3&#xff0c;输入信息&#xff0c;进行数据库连接 4&#xff0c;创建 statement对象 5&#xff0c;执行sql语句 6&#xff0c;如果是查询操作&#xff0c;利用ResultSet处理数据&#xf…

【动手学深度学习】(十一)池化层+LeNet

文章目录 一、池化层1.理论知识2.代码 二、LeNet1.理论知识2.代码实现 【相关总结】nn.MaxPool2d() 卷积层对位置比较敏感 一、池化层 1.理论知识 二维最大池化 填充、步幅和多个通道 池化层与卷积层类似&#xff0c;都具有填充和步幅没有可学习的参数在每个输入通道应用池…

【出现模块node_modules里面包找不到】

#pic_center R 1 R_1 R1​ R 2 R^2 R2 目录 一、出现的问题二、解决办法三、其它可供参考 一、出现的问题 在本地运行 npm run docs:dev之后&#xff0c;出现 Error [ERR_MODULE_NOT_FOUND]: Cannot find package Z:\Blog\docs\node_modules\htmlparser2\ imported from Z:\Blo…

CCF计算机软件能力认证202309-1坐标变换(其一)(C语言)

ccf-csp计算机软件能力认证202309-1坐标变换&#xff08;其一&#xff09;(C语言版) 题目内容&#xff1a; 问题描述 输入格式 输出格式 样例输入 3 2 10 10 0 0 10 -20 1 -1 0 0样例输出 21 -11 20 -10样例解释 评测用例规模与约定 解题思路 1.第一步分析问题&…

Redux Toolkit(RTK)在React tsx中的使用

一个需求: header组建中有一个搜索框,然后这个搜索框在其他页面路由上都可以使用:例如这两个图共用顶部的搜索框; 我之前的做法就是组建传值, 在他们header 组建和 PageA ,B 的父级组件上定一个值,然后顶部变化传到父级组件,在从父级组件传到page组件,有点繁琐,现在说一下利用…