Swift 如何实现自定义 Tab Bar

前言

每个 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] _ inguard 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: CustomTabIteminit(item: CustomTabItem) {self.item = itemsuper.init(nibName: nil, bundle: nil)}required init?(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}
}

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

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

private func setupProperties() {tabBar.isHidden = truecustomTabBar.translatesAutoresizingMaskIntoConstraints = falsecustomTabBar.addShadow()selectedIndex = 0let 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.mzph.cn/news/198701.shtml

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

相关文章

用金鸣识别实现纸质合同转word的几个解决方案

合同是我们日常生活和工作中经常碰到的文件类型&#xff0c;如果您需要将纸合同转为可编辑的Word文档怎么办呢&#xff1f; 如果是一般无表格的合同比较好处理&#xff0c;一般的OCR软件都能应付&#xff0c;只是识别率高低的问题&#xff08;好的OCR识别率高&#xff0c;后期…

用js自定义一个(v-model)vModel双向绑定函数

vue中的v-model是双向绑定的, 我们自己用JavaScript实现一个双向绑定vModel函数。 // element 元素或者#id,.class,div 得是input标签 // data 对象 // 将要绑定property 对象中的key<input class"vmodel"/>function vModel(element, data, property) {if (…

优雅草蜻蜓I即时通讯·水银版私有化部署之java服务端搭建教程-01

目录 前言1 1 安装 mongodb2 2 安装 redis3 3. 安装jdk3 4 解压 spring-boot-imapi3 5.开始安装 消息队列组件 rocket4 6. 安装推送服务5 7. 安装 message-push5 8. 安装uplooad 服务5 9&#xff1a; 安装nginx 服务7 1.不需要SSL7 2.需要SSL7 五&#xff1a;编译…

LORA概述: 大语言模型的低阶适应

LORA概述: 大语言模型的低阶适应 LORA: 大语言模型的低阶适应前言摘要论文十问实验RoBERTaDeBERTaGPT-2GPT-3 结论代码调用 LORA: 大语言模型的低阶适应 前言 LoRA的核心思想在于优化预训练语言模型的微调过程&#xff0c;通过有效地处理权重矩阵的变化&#xff08;即梯度更新…

常用sql记录

备份一张表 PostgreSQL CREATE TABLE new_table AS SELECT * FROM old_table;-- 下面这个比上面好&#xff0c;这个复制表结构时&#xff0c;会把默认值、约束、注释都复制 CREATE TABLE new_table (LIKE old_table INCLUDING ALL) WITHOUT OIDS; INSERT INTO new_table SELE…

跨网文件摆渡系统:安全、可控的数字传输桥梁

在企业高度信息化的时代&#xff0c;数据的流通与共享已经成为企业、组织乃至个人之间不可或缺的沟通方式。然而&#xff0c;在数据流通的过程中&#xff0c;我们经常会遇到各种难题和挑战&#xff0c;尤其是当涉及到不同网络环境之间的文件传输。这不仅需要保证文件的安全性&a…

09、pytest多种调用方式

官方用例 # content of myivoke.py import sys import pytestclass MyPlugin:def pytest_sessionfinish(self):print("*** test run reporting finishing")if __name__ "__main__":sys.exit(pytest.main(["-qq"],plugins[MyPlugin()]))# conte…

烤鱼纸包鱼外卖配送小程序商城作用是什么

烤鱼、纸包鱼等餐品是聚会、娱乐、餐食等场景中常见的餐品&#xff0c;到店和外送都有较高需求度&#xff0c;对消费者来说需要找到美味的餐厅和快速享受到美食的流程&#xff1b;对商家来说是如何找到更多消费&#xff0c;并且能快速转化和持续复购及相应的管理。 线下竞争激…

第三方支付原理

1.什么是第三方支付 所谓第三方支付&#xff0c;就是一些和各大银行签约、并具备一定实力和信誉保障的第三方独立机构提供的交易支持平台。在通过第三方支付平台的交易中&#xff0c;买方选购商品后&#xff0c;使用第三方平台提供的账户进行货款支付&#xff0c;由第三方通知卖…

【langchain实战】开源项目-RasaGpt

1、概述 RasaGpt是一个建立在 Rasa 和 Langchain 之上的没有显示界面的LMM聊天机器人平台。它是一个Rasa和Telegram这种利用像Langchain这样的LMM库进行索引、检索和上下文注入的样板及参考实现。 开源地址&#xff1a; GitHub - paulpierre/RasaGPT: &#x1f4ac; RasaGPT is…

接口压测指南

接口压测指南 一、 为什么需要进行接口压测二 、接口压测的目标是什么三、 用什么工具进行接口压测四、 接口压测核心指标4.1 JMeter的报告模板4.2 ApiPost报告模板 五、 接口慢如何排查5.1 大体排查思路5.2 排查工具5.3 压测经验 一、 为什么需要进行接口压测 突然有一天领导…

漏洞复现--万户ezoffice wpsservlet任意文件上传

免责声明&#xff1a; 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直…

虚拟机-桥接模式连接

文章目录 1.查看宿主机再用的IP信息2.桥接模式-虚拟机设置VMware设置虚拟机设置重启网络服务 1.查看宿主机再用的IP信息 ipconfig /all 注&#xff1a; 在虚拟机中要设置同网段的ip设置同一个子网掩码设置同一个网关设置同一个DNS服务器 2.桥接模式-虚拟机设置 VMware设置 虚…

Python 高性能 web 框架 - FastApi 全面指南

原文&#xff1a;Python 高性能 web 框架 - FastApi 全面指南 - 知乎 一、简介 FastAPI 是一个用于构建 API 的现代、快速&#xff08;高性能&#xff09;的 web 框架&#xff0c;使用 Python 3.6 并基于标准的 Python 类型提示。 它具有如下这些优点&#xff1a; 快速&…

C盘爆满,python pip无法安装应用

解决方法1 C盘扩容 从其他盘压缩空间&#xff0c;C盘使用压缩的空间进行扩容&#xff0c;治标不治本&#xff0c;以后C盘还会越来越大 解决方法2 转移pip安装目录 1. 获取显示pip安装目录 C:\Users\biewang>pip show pip Name: pip Version: 23.3.1 Summary: The PyPA r…

【实战技能】 单步运行源码分析,一期视频整明白FreeRTOS内核源码框架和运行机制,RTOS Trace链表功能展示

从源码的角度来看&#xff0c;OS内核源码就是通过各种链表组装起来的&#xff0c;FreeRTOS就是下面几个链表组成的。FreeRTOS的调度&#xff0c;任务切换就是倒腾这几个链表。而其它的几款OS是一个链表就一撸到底了&#xff0c;FreeRTOS是搞了好几个。所以视频里面就重点介绍下…

公募REITs交易规则详解

普通投资者是否可以参与基础设施公募REITs交易&#xff1f; 基础设施公募 REITs 采取封闭式运作,符合法定条件并经交易所依法审核同意后&#xff0c;可以上市交易。基础设施公募 REITs 的认购和交易实施适当性管理制度。尽管对普通投资者参与基础设施公募 REITs 的认购和交易没…

boost::throw_exception错误:修改VS代码生成异常选项为/EHsc

VS2013添加boost头文件和库文件路径后&#xff0c;代码编译报错&#xff1a; 错误 LNK2019 无法解析的外部符号 “void __cdecl boost::throw_exception(class std::exception const &)” (?throw_exceptionboostYAXAEBVexceptionstdZ)&#xff0c;该符号在函数 “public:…

Python实现股票回测框架搭建,回测交易策略可行性。

文章目录 什么是回测框架?回测框架交易历史数据回测报告回测示例关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③Python小游戏源码五、面试资料六、Python兼职渠道…

Footprint Analytics x Future3 万字研报:AI 与 Web3 数据行业融合的现状、竞争格局与未来机遇探析(上)

GPT的横空出世将全球的目光吸引至大语言模型&#xff0c;各行各业都尝试着利用这个“黑科技”提高工作效率&#xff0c;加速行业发展。Future3 Campus携手Footprint Analytics共同深入研究AI与Web3结合的无限可能&#xff0c;联合发布了《AI与Web3数据行业融合现状、竞争格局与…