当前位置: 首页 > news >正文

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 设计师和开发人员提供了一个更灵活的选择,使得移动应用的界面更加吸引人。希望本文的内容对于那些希望提升用户体验的开发人员和设计师来说是有益的,能够帮助他们更好地实现他们的创意和设计理念。

相关文章:

Swift 如何实现自定义 Tab Bar

前言 每个 UI 设计师都喜欢美丽而有动画效果的 Tab Bar。然而&#xff0c;对于开发人员来说&#xff0c;实现这种设计可能是一场噩梦。当然&#xff0c;使用 Apple 的原生 Tab Bar 组件并专注于更有趣的事情&#xff0c;比如业务逻辑的实现&#xff0c;会更容易。但如果我们必…...

mysql 语言学习

整理了一下 mysql 操作语言&#xff0c;不是很全&#xff0c;部分地方也许需要修改&#xff0c;先放上来&#xff0c;有时间再慢慢完善。 一、数据库操作 连接数据库 $ sudo mysql [-h ip] -u root -p [-P 3306] 初始化数据库 $ mysql_secure_installation备份数据库 # 备…...

微信小程序基础bug

1.苹果11手机小程序请求数据不显示 设置-》隐私-》分析与改进-》开启 ”与开发者共享“ 2.<navigator>组件回退delta不成功 tabBar 页面是不能实现后退的效果的. 因为, 当我们跳转到 tabBar 页面&#xff0c;会关闭其他所有非tabBar 页面,所以当处于 tabBar 页面时, 无…...

13、pytest为失败的断言定义自己的解释

官方实例 # content of ocnftest.py from test_foocompare import Foodef pytest_assertrepr_compare(op, left, right):if isinstance(left, Foo) and isinstance(right, Foo) and op "":return["Comparing Foo instances:",f" vals:{left.val} !…...

Flink优化——数据倾斜(二)

目录 数据倾斜 判断是否存在数据倾斜 数据倾斜的解决 KeyBy之前发生数据倾斜 KeyBy之后发生的数据倾斜 聚合操作存在数据倾斜 窗口聚合操作存在数据倾斜 数据倾斜 判断是否存在数据倾斜 相同 Task 的多个 Subtask 中&#xff0c;个别 Subtask 接收到的数据量明显大于其…...

Unity打包到Webgl平台以及遇到的问题

Unity打包到Webgl平台以及遇到的问题 参考网站 Unity打包WebGL的全过程及在打包和使用过程中会遇到的问题(本地测试)-CSDN博客 unity打包到Webgl 并配置能正常运行 这里我用的是Unity2022.3.3f1c1版本 有两种方法 1、配置本地web服务 2、安装vsCode>添加插件LiveServe…...

c语言编程题经典100例——(90~95例)

1,写一个函数&#xff0c;实现数字的加密和解密。 下面是一个简单的C语言函数&#xff0c;可以实现数字的加密和解密。这个函数采用简单的加密算法&#xff0c;将输入的数字乘以一个固定的密钥&#xff0c;然后加上一个固定的偏移量。解密过程就是将加密后的数字减去偏移量&am…...

Redis核心知识点总结

1.Redis介绍 Redis 是 NoSQL&#xff0c;但是可处理 1 秒 10w 的并发&#xff08;数据都在内存中&#xff09; 使用 java 对 redis 进行操作类似 jdbc 接口标准对 mysql&#xff0c;有各类实现他的实现类&#xff0c;我们常用的是 druid 其中对 redis&#xff0c;我们通常用 J…...

stm32Flash操作

//G0B0 flash大小 0x08000000-0x0807FFFF 512K(0400 1K)//2k 1页 //初始化标记数据地址 放最前面 脱机烧写器可擦除掉 #define CONST_INITMARKDATA_ADDRESS (0x0807D000UL) //2k 1页 //射频数据地址 #define CONST_FREQDATA_ADDRESS (0x0807F000UL) //2…...

云原生系列1

1、虚拟机集群环境准备 VirtualBox类似vmware的虚拟化软件&#xff0c;去官网https://www.virtualbox.org/下载最新版本免费的&#xff0c;VirtualBox中鼠标右ctrl加home跳出鼠标到wins中。 VirtualBox安装步骤 https://blog.csdn.net/rfc2544/article/details/131338906 cent…...

设计原则 | 里式替换原则

一、里式替换原则&#xff08;Liskov Substitution Principle &#xff09; 1、原理 子类型必须能替换掉它们的基类型&#xff0c;在使用继承时&#xff0c;遵循里式替换原则&#xff0c;在子类中尽量不要重写父类中的方法。里式替换原则告诉我们&#xff0c;继承实际上让两个…...

第7节:Vue3 动态绑定多个属性

可以使用v-bind指令将多个属性动态绑定到元素上。以下是一个简单的实例&#xff1a; <template><view class"container"><text v-bind"dynamicProps">{{ message }}</text><button click"toggleActive">切换激活…...

【文件上传系列】No.1 大文件分片、进度图展示(原生前端 + Node 后端 Koa)

分片&#xff08;500MB&#xff09;进度效果展示 效果展示&#xff0c;一个分片是 500MB 的 分片&#xff08;10MB&#xff09;进度效果展示 大文件分片上传效果展示 前端 思路 前端的思路&#xff1a;将大文件切分成多个小文件&#xff0c;然后并发给后端。 页面构建 先在页…...

性能测试 —— Jmeter分布式测试的注意事项和常见问题

Jmeter是一款开源的性能测试工具&#xff0c;使用Jmeter进行分布式测试时&#xff0c;也需要注意一些细节和问题&#xff0c;否则可能会影响测试结果的准确性和可靠性。 Jmeter分布式测试时需要特别注意的几个方面 1. 参数化文件的位置和内容 如果使用csv文件进行参数化&#x…...

“SRP模型+”多技术融合在生态环境脆弱性评价模型构建、时空格局演变分析与RSEI 指数的生态质量评价及拓展应用

近年来&#xff0c;国内外学者在生态系统的敏感性、适应能力和潜在影响等方面开展了大量的生态脆弱性研究&#xff0c;他们普遍将生态脆弱性概念与农牧交错带、喀斯特地区、黄土高原区、流域、城市等相结合&#xff0c;评价不同类型研究区的生态脆弱特征&#xff0c;其研究内容…...

总结|哪些平台有大模型知识库的Web API服务

截止2023/12/6 笔者个人的调研&#xff0c;有三家有大模型知识库的web api服务&#xff1a; 平台类型文档数量文档上传并解析的结构api情况返回页码文心一言插件版多文档有问答api&#xff0c;文档上传是通过网页进行上传有&#xff0c;而且是具体的chunk id&#xff0c;需要设…...

TOMCAT9安装

1、官网下载 2、解压到任意盘符&#xff0c;注意路径不要有中文 3、环境变量 path 下 配置 %CATALINA_HOME%\bin 4、找到tomcat9/bin&#xff0c; 点击 start.bat启动 tomcat...

QT中时间时区处理总结

最近项目中要做跨国设备时间校正功能&#xff0c;用到了时区时间&#xff0c;在此做一下记录。 目录 1.常见时区名 2.测试代码 3.运行效果 1.常见时区名 "Pacific/Midway": "中途岛 (UTC-11:00)", …...

OpenAtom OpenHarmony三方库创建发布及安全隐私检测

OpenAtom OpenHarmony 三方库&#xff08;以下简称“三方库”或“包”&#xff09;&#xff0c;是经过验证可在 OpenHarmony 系统上可重复使用的软件组件&#xff0c;可帮助开发者快速开发 OpenHarmony 应用。三方库根据其开发语言分为 2 种&#xff0c;一种是使用 JavaScript …...

【1】一文读懂PyQt简介和环境搭建

目录 1. PyQt简介 1.1. Qt 1.2. PyQt 1.3. 关于PyQt和PySide 2. 通过pip安装PyQt5 3. 无法运行处理 4. VSCode配置PYQT插件 PyQt官网:Riverbank Computing | Introduction 1. PyQt简介 PyQt是一套Python的GUI开发框架,即图形用户界面开发框架。 Python中经常使用的GU…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

SciencePlots——绘制论文中的图片

文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了&#xff1a;一行…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计

随着大语言模型&#xff08;LLM&#xff09;参数规模的增长&#xff0c;推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长&#xff0c;而KV缓存的内存消耗可能高达数十GB&#xff08;例如Llama2-7B处理100K token时需50GB内存&a…...

中医有效性探讨

文章目录 西医是如何发展到以生物化学为药理基础的现代医学&#xff1f;传统医学奠基期&#xff08;远古 - 17 世纪&#xff09;近代医学转型期&#xff08;17 世纪 - 19 世纪末&#xff09;​现代医学成熟期&#xff08;20世纪至今&#xff09; 中医的源远流长和一脉相承远古至…...

视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)

前言&#xff1a; 最近在做行为检测相关的模型&#xff0c;用的是时空图卷积网络&#xff08;STGCN&#xff09;&#xff0c;但原有kinetic-400数据集数据质量较低&#xff0c;需要进行细粒度的标注&#xff0c;同时粗略搜了下已有开源工具基本都集中于图像分割这块&#xff0c…...

FFmpeg:Windows系统小白安装及其使用

一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】&#xff0c;注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录&#xff08;即exe所在文件夹&#xff09;加入系统变量…...