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

SwiftUI 6.0(iOS/iPadOS 18)中全新的 Tab 以及 Sidebar+悬浮 TabView 样式

在这里插入图片描述

概览

看来苹果一直对 iPadOS 中标签栏(TabView)不甚满意。这不,在 WWDC 2024 中苹果又对 TabView 外观做了大幅度的进化。

在这里插入图片描述

现在我们可以在顶部悬浮条和左侧的 Sidebar 两种不同布局之间恣意切换 TabView 的外观啦。而且,这在 SwiftUI 6.0 中显得尤为简单。

在本篇博文中,您将学到如下内容:

  • 概览
  • 1. iPadOS 18 中全新的 Sidebar+悬浮 TabView 样式
  • 2. 新 TabView 样式在 iOS 18 中的表现
  • 3. SwiftUI 6.0 新的 Tab 和 TabSection 原生视图
  • 4. 让 Tab 懂你所选
  • 5. 调整 Tab 的显示位置
  • 总结

看来 iPadOS 18 对 TabView 外观和功能都做了重量级的升级,那小伙伴们还等什么呢?

Let‘s find out!!!😉


1. iPadOS 18 中全新的 Sidebar+悬浮 TabView 样式

为了最大化利用屏幕空间,从 iPadOS 18 开始苹果有史以来第一次将位于底部的 TabView:

在这里插入图片描述

神奇的变为悬浮条置于窗口的顶部:

在这里插入图片描述

从上面两张图中我们可以对比在 iPadOS 18 中系统默认的时钟 App 中标签栏的新外观。

当需要显示悬浮条内额外标签页的内容时,我们还可以轻按最左侧的展开按钮将其变形为 Sidebar 模式以便将所有标签项一览无遗:
在这里插入图片描述

不仅如此,我们还可以恣意调整 Sidebar 和悬浮条中标签项的位置、将它们打包到组中显示,甚至定制它们的外观:

在这里插入图片描述

在这里插入图片描述

按苹果的话来说:标签栏的新外观可以最大化利用显示空间,并让我们的 App 极具现代化外观的范儿。 我们可以在悬浮条和 Sidebar 两者间随意切换从而解决了“麻雀虽小”和“面面俱到”之间鱼与熊掌不可兼得的问题。

2. 新 TabView 样式在 iOS 18 中的表现

为了能保持 iPadOS 18 中新 TabView 的延展性,在 iOS 18 中新的标签栏会“退化”为之前经典的样式。

在这里插入图片描述

与之前经典 TabView 外观类似:新的 TabView 在 iOS 18 中右侧额外显示不下的内容也会统统放到单独的 More 标签项中去。

所不同的是,iOS 18 新标签栏中的标签组(TabSection)会消失的“无影无踪”,其中的标签会被直接放在底部的标签栏中。

有些小伙伴们可能会好奇:iPhone pro max 大屏设备横屏时新的 TabView 会有怎样的表现?
在这里插入图片描述

上面就是 iPhone pro max 横屏时新标签栏在 iOS 18 中的样子。可以看到:除了最右侧 More 标签中的内容被平铺显示以外和之前并无二致。所以想利用大屏 iPhone 设备适配新 TabView 悬浮条外观的小伙伴们可能要暂时失望了。

了解完 iOS 18 中新标签栏的模样之后,下面就让我们来看看如何用代码展现它们吧。

在 SwiftUI 6.0 中适配 TabView 新外观超级简单,然我们马上来撸码为证!

3. SwiftUI 6.0 新的 Tab 和 TabSection 原生视图

从 SwiftUI 6.0 开始,苹果增加了全新的 Tab 和 TabSection 原生视图,专门为新 TabView 来效“犬马之劳”:

在这里插入图片描述
在这里插入图片描述
其中,Tab 被用来取代原来的 tabItem() 修改器,而 TabSection 则用来将多个 Tab 汇聚成组。

在 iPadOS 18 之前,我们是这样描述 TabView 外观的:

struct ContentView: View {@Environment(\.horizontalSizeClass) var hSize@State var tabSelecting = TabValue.allTopics@State var moveFromLeading = truevar body: some View {TabView(selection: $tabSelecting) {AnimSubView(title: "Main", bgColor: .white, imageName: "main", isMoveFromLeading: moveFromLeading).tabItem {Label("Main", systemImage: "house")}.tag(TabValue.main)AnimSubView(title: "This is the blog page", bgColor: .yellow, isMoveFromLeading: moveFromLeading).tabItem {Label("Topics", systemImage: "pencil")}.tag(TabValue.allTopics)AnimSubView(title: "SwiftUI topic", bgColor: .purple.opacity(0.3), isMoveFromLeading: moveFromLeading).tabItem {Label("SwiftUI", systemImage: "swift")}.tag(TabValue.swiftUI)AnimSubView(title: "Concurrency topic", bgColor: .green, isMoveFromLeading: moveFromLeading).tabItem {Label("Concurrency", systemImage: "timelapse")}.tag(TabValue.concurrency)AnimSubView(title: "Persistence topic", bgColor: .blue.opacity(0.3), isMoveFromLeading: moveFromLeading).tabItem {Label("Persistence", systemImage: "swiftdata")}.tag(TabValue.persistence)AnimSubView(title: "Search the site", bgColor: .cyan, isMoveFromLeading: moveFromLeading).tabItem {Label("Search", systemImage: "magnifyingglass")}.tag(TabValue.search)}.safeAreaInset(edge: .bottom) {Text("当前选中:\(tabSelecting)").font(.title2.weight(.heavy)).foregroundStyle(.white.gradient).padding().background(RoundedRectangle(cornerRadius: 15).foregroundStyle(.mint.gradient)).shadow(radius: 3.0)}.safeAreaPadding(.bottom, hSize == .compact ? 100 : 0).onChange(of: tabSelecting) {old,new in// 为标签切换增加转场动画支持...}.tint(.pink)}
}

可以看到:在 TabView 中我们使用 tabItem() 和 tag() 修改器用来构建每一个标签页。

而在新的 iPadOS 18 和 iOS 18 中,我们彻底抛弃了这两个修改器而改为使用 Tab 和 TabSection 视图来描述每个标签页:

struct ContentView: View {@State var moveFromLeading = truevar body: some View {TabView {Tab("Main", systemImage: "house") {AnimSubView(title: "Main", bgColor: .white, imageName: "main", isMoveFromLeading: moveFromLeading)}TabSection("Blog") {Tab("All topics", systemImage: "pencil") {AnimSubView(title: "This is the blog page", bgColor: .yellow, isMoveFromLeading: moveFromLeading)}Tab("SwiftUI", systemImage: "swift") {AnimSubView(title: "SwiftUI topic", bgColor: .purple.opacity(0.3), isMoveFromLeading: moveFromLeading)}Tab("Concurrency", systemImage: "timelapse") {AnimSubView(title: "Concurrency topic", bgColor: .green, isMoveFromLeading: moveFromLeading)}Tab("Persistence", systemImage: "swiftdata") {AnimSubView(title: "Persistence topic", bgColor: .blue.opacity(0.3), isMoveFromLeading: moveFromLeading)}}// 更多的 TabSectionsTab(role: .search) {AnimSubView(title: "Search the site", bgColor: .cyan, isMoveFromLeading: moveFromLeading)}}.tint(.pink)}
}

小伙伴们可以留意一下,在上面的代码中我们是如何使用 TabSection 将若干 Tab 聚合成组的。

运行可以看到,原本底部的标签栏化身为悬浮条一跃横空出世了:

在这里插入图片描述

要想让标签栏进一步支持 Sidebar 切换显示,我们只需将 .sidebarAdaptable 样式应用在 TabView 上即可:

TabView {...
}
.tabViewStyle(.sidebarAdaptable)

在这里插入图片描述


值得注意的是:要想 iOS 18 之前的标签栏旧语法支持悬浮条显示,我们无需修改半行代码。

而只需一行 tabViewStyle(.sidebarAdaptable) 语句的加持,我们也同样可以让旧标签栏支持 Sidebar 样式。


4. 让 Tab 懂你所选

现在我们新的 TabView 已经灵动的“跃然于纸上”了,不过如果我们希望动态跟踪用户选择了哪个 Tab 又该如何是好呢?

在 iOS 18 中使用 tag() 标识标签页的方式已经被废弃,取而代之的是全新的包含 value 参数的 Tag 构造器方法:

在这里插入图片描述

注意该 Tab 构造器必须放在 TabView(selection:) 特定的构造器闭包中才能编译通过。

struct ContentView: View {@Environment(\.horizontalSizeClass) var hSize@State var tabSelecting = TabValue.allTopics@State var moveFromLeading = truevar body: some View {TabView(selection: $tabSelecting) {Tab("Main", systemImage: "house", value: .main) {AnimSubView(title: "Main", bgColor: .white, imageName: "main", isMoveFromLeading: moveFromLeading)}TabSection("Blog") {Tab("All topics", systemImage: "pencil", value: TabValue.allTopics) {AnimSubView(title: "This is the blog page", bgColor: .yellow, isMoveFromLeading: moveFromLeading)}Tab("SwiftUI", systemImage: "swift", value: .swiftUI) {AnimSubView(title: "SwiftUI topic", bgColor: .purple.opacity(0.3), isMoveFromLeading: moveFromLeading)}                Tab("Concurrency", systemImage: "timelapse", value: .concurrency) {AnimSubView(title: "Concurrency topic", bgColor: .green, isMoveFromLeading: moveFromLeading)}Tab("Persistence", systemImage: "swiftdata", value: .persistence) {AnimSubView(title: "Persistence topic", bgColor: .blue.opacity(0.3), isMoveFromLeading: moveFromLeading)}}Tab(value: .search, role: .search) {AnimSubView(title: "Search the site", bgColor: .cyan, isMoveFromLeading: moveFromLeading)}}.tabViewStyle(.sidebarAdaptable).safeAreaInset(edge: .bottom) {Text("当前选中:\(tabSelecting)").font(.title2.weight(.heavy)).foregroundStyle(.white.gradient).padding().background(RoundedRectangle(cornerRadius: 15).foregroundStyle(.mint.gradient)).shadow(radius: 3.0)}.safeAreaPadding(.bottom, hSize == .compact ? 100 : 0).onChange(of: tabSelecting) {old,new in// 标签切换转场动画支持代码从略...}.tint(.pink)}
}

现在,我们已经能够捕获到用户当前选择的标签页啦:

在这里插入图片描述

5. 调整 Tab 的显示位置

除了在 TabView 中用 Tab 和 TabSection 妥善放置各个标签页以外,我们还可以进一步决定它们到底能够在何处显示。

这是通过 tabPlacement() 修改器方法来实现的:

在这里插入图片描述

其中对于 TabPlacement 类型的参数值我们有 3 种选择:

在这里插入图片描述

比如假若我们希望将 TabSection(“Blog”) 内部中间两个 Tab 始终放在悬浮条里,我们可以分别为其调用 .tabPlacement(.pinned) 修改器方法:

TabSection("Blog") {Tab("All topics", systemImage: "pencil", value: TabValue.allTopics) {AnimSubView(title: "This is the blog page", bgColor: .yellow, isMoveFromLeading: moveFromLeading)}Tab("SwiftUI", systemImage: "swift", value: .swiftUI) {AnimSubView(title: "SwiftUI topic", bgColor: .purple.opacity(0.3), isMoveFromLeading: moveFromLeading)}.tabPlacement(.pinned)Tab("Concurrency", systemImage: "timelapse", value: .concurrency) {AnimSubView(title: "Concurrency topic", bgColor: .green, isMoveFromLeading: moveFromLeading)}.tabPlacement(.pinned)Tab("Persistence", systemImage: "swiftdata", value: .persistence) {AnimSubView(title: "Persistence topic", bgColor: .blue.opacity(0.3), isMoveFromLeading: moveFromLeading)}
}

现在,这两个标签项会一如既往的驻留在顶部的悬浮条中,棒棒哒:

在这里插入图片描述

总结

在本篇博文中,我们介绍了 SwiftUI 6.0(iPadOS 18/iOS 18)新标签页中新增的 Tab 和 TabSection 原生视图,并进一步讨论了如何让 TabView 支持 Sidebar 样式以及其它有趣的新特性。

感谢观赏,再会!😎

相关文章:

  • 数据分析第三讲:numpy的应用入门(二)
  • 【LLM之RAG】RAT论文阅读笔记
  • C++ 矩阵乘法
  • Linux 6.10也引进了蓝屏机制
  • LeetCode热题3.无重复的最长字串
  • Java练习题1
  • Java数据结构与算法——稀疏数组和队列
  • Webrtc支持FFMPEG硬解码之NVIDA(二)
  • golang:对struct排序的方法
  • 简述为什么Vue采用异步渲染 ?
  • idea 创建properties文件,解决乱码
  • 【Java】已解决java.sql.SQLTimeoutException异常
  • React-Redux学习笔记(自用)
  • Java Stream流应用
  • Android SurfaceFlinger——概述(一)
  • canvas绘制圆角头像
  • css系列之关于字体的事
  • Javascript编码规范
  • PAT A1050
  • ReactNative开发常用的三方模块
  • React系列之 Redux 架构模式
  • scala基础语法(二)
  • Vue.js源码(2):初探List Rendering
  • Work@Alibaba 阿里巴巴的企业应用构建之路
  • 阿里云应用高可用服务公测发布
  • 初识 webpack
  • 前端知识点整理(待续)
  • 如何正确配置 Ubuntu 14.04 服务器?
  • 适配mpvue平台的的微信小程序日历组件mpvue-calendar
  • 微信小程序填坑清单
  • 3月27日云栖精选夜读 | 从 “城市大脑”实践,瞭望未来城市源起 ...
  • Java总结 - String - 这篇请使劲喷我
  • 完善智慧办公建设,小熊U租获京东数千万元A+轮融资 ...
  • 正则表达式-基础知识Review
  • ​如何防止网络攻击?
  • ​虚拟化系列介绍(十)
  • # 移动硬盘误操作制作为启动盘数据恢复问题
  • (web自动化测试+python)1
  • (zt)最盛行的警世狂言(爆笑)
  • (安全基本功)磁盘MBR,分区表,活动分区,引导扇区。。。详解与区别
  • (附源码)计算机毕业设计SSM疫情下的学生出入管理系统
  • (教学思路 C#之类三)方法参数类型(ref、out、parmas)
  • (每日一问)操作系统:常见的 Linux 指令详解
  • (区间dp) (经典例题) 石子合并
  • (十六)一篇文章学会Java的常用API
  • (最新)华为 2024 届秋招-硬件技术工程师-单板硬件开发—机试题—(共12套)(每套四十题)
  • .bat文件调用java类的main方法
  • .NET 8 中引入新的 IHostedLifecycleService 接口 实现定时任务
  • .net MySql
  • .net 连接达梦数据库开发环境部署
  • .NET 使用配置文件
  • .Net(C#)常用转换byte转uint32、byte转float等
  • .NetCore发布到IIS
  • .Net多线程Threading相关详解
  • .skip() 和 .only() 的使用