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

news2024/11/18 10:32:26

在这里插入图片描述

概览

看来苹果一直对 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 = true
    
    var 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 = true
    
    var 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)
                }
            }

            // 更多的 TabSections

            Tab(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 = true
    
    var 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 样式以及其它有趣的新特性。

感谢观赏,再会!😎

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

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

相关文章

ubuntu 18 虚拟机安装(3)安装mysql

ubuntu 18 虚拟机安装(3)安装mysql 参考 https://cloud.tencent.com/developer/article/1700780 技术分享 | MySQL 设置管理员密码无法生效一例 https://cloud.tencent.com/developer/article/2014384 在Ubuntu18.04上安装MySQL | 超级详细…

字节豆包 MarsCode:AI 开发工具

MarsCode 是豆包旗下的智能编程助手,类似 GitHub Copilot 提供以智能代码补全为代表的核心能力,简单试用了下,免费,使用时需要手机号登录,代码补全还算 ok,聊天功能就有点差了。 还包括一个 AI 原生 IDE&am…

EPLAN批量修改文字大小

在项目设计过程中,往往要批量调整文字的大小,如何批量修改文字大小: 点击需要调整的相同类的文字 右键 “属性”,然后在分配里找到“属性放置,设备标识符”这一栏 看下文字的属性在第几层 在项目数据找到层管理&…

道路救援入驻派单小程序开源版开发

道路救援入驻派单小程序开源版开发 1、用户立即救援 2、后台收到救援通知,派单救援师傅. 道路救援入驻派单小程序通常会包含一系列功能,旨在方便救援服务提供商、用户和后台管理系统之间的交互。以下是一个可能的功能列表: 用户端功能&…

前端框架中的前端打包(Bundling)和前端构建工具(Build Tools)的作用

聚沙成塔每天进步一点点 本文回顾 ⭐ 专栏简介前端框架中的前端打包(Bundling)和前端构建工具(Build Tools)的作用1. 引言2. 前端打包(Bundling)2.1 概述2.2 常见的打包工具2.2.1 Webpack2.2.2 Parcel 2.3 …

合约期VS优惠期,搞明白他们的区别才能避免很多坑!

在购买流量卡时,相信大家也都发现了,市面上的不少套餐都是有合约期和优惠期的,尤其是联通和移动,那么,什么是合约期?什么又是优惠期呢? ​ 其实,目前很多在网上办理的大流量卡都是有…

静态图和动态图中的自动求导机制详解

01 静态图与动态图的区别 之前在 [1] 中提到过,自动求导(AutoDiff)机制是当前深度学习模型训练采用的主要方法,而在静态图和动态图中对于自动求导的处理是不一样的。作为前置知识,这里简单进行介绍。 我们都知道静态…

【深度学习】tensorboard的使用

目前正在写一个训练框架,需要有以下几个功能: 1.保存模型 2.断点继续训练 3.加载模型 4.tensorboard 查询训练记录的功能 命令: tensorboard --logdirruns --host192.168.112.5 效果: import torch import torch.nn as nn impor…

排序算法(2)之选择排序----直接选择排序和堆排序

个人主页:C忠实粉丝 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C忠实粉丝 原创 排序算法(2)之交换排序----冒泡排序和堆排序 收录于专栏【数据结构初阶】 本专栏旨在分享学习数据结构学习的一点学习笔记,欢迎大家在评论区交流讨论…

【系统架构设计师】七、信息安全技术基础知识(信息安全的概念|信息安全系统的组成框架|信息加解密技术)

目录 一、信息安全的概念 1.1 信息安全的基本要素和范围 1.2 信息存储安全 1.3 网络安全 二、信息安全系统的组成框架 2.1 技术体系 2.2 组织机构体系 2.3 管理体系 三、 信息加解密技术 3.1 数据加密 3.2 对称加密技术 3.3 非对称加密算法 3.4 数字信封 3.5 信…

信息系统项目管理师(项目管理师)

项目管理者再坚持“聚焦于价值”原则时,应该关注的关键点包括:1价值是项目成功的最终指标;2价值可以再整个项目进行期间、项目结束或完成后实现;3价值可以从定性和/或定量的角度进行定义和衡量;4以成果为导向&#xff…

鸿蒙开发系统基础能力:【@ohos.pasteboard (剪贴板)】

剪贴板 说明: 本模块首批接口从API version 6开始支持。后续版本的新增接口,采用上角标单独标记接口的起始版本。 导入模块 import pasteboard from ohos.pasteboard;属性 系统能力: 以下各项对应的系统能力均为SystemCapability.MiscServices.Pasteb…

ubuntu 18 虚拟机安装(1)

ubuntu 18 虚拟机安装 ubuntu 18.04.6 Ubuntu 18.04.6 LTS (Bionic Beaver) https://releases.ubuntu.com/bionic/ 参考: 设置固定IP地址 https://blog.csdn.net/wowocpp/article/details/126160428 https://www.jianshu.com/p/1d133c0dec9d ubuntu-18.04.6-l…

相亲交友微信小程序系统源码

开启浪漫邂逅新篇章 相亲交友——随着年龄的增长,越来越多的人开始关注自己的婚姻问题,为了提高相亲服务的质量,这款应用就可以拓宽在线社交网络范围。​ 💑 引言:邂逅爱情的新方式 在繁忙的都市生活中,寻…

《梦醒蝶飞:释放Excel函数与公式的力量》6.1 DATE函数

6.1 DATE函数 第一节:DATE函数 1)DATE函数概述 DATE函数是Excel中的一个内置函数,用于根据指定的年、月、日返回对应的日期序列号。这个函数非常有用,尤其是在处理日期数据时,它可以帮助你构建特定的日期&#xff0…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《计及氢储能与需求响应的路域综合能源系统规划方法》

本专栏栏目提供文章与程序复现思路,具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

DOOPRIME:美联储降息何时到来?经济数据是关键

摘要: 美联储理事库克在纽约经济俱乐部的演讲中表示,降息时机取决于未来的经济数据和通胀走势。她预期,明年通胀将显著放缓,其中核心商品通胀可能保持轻微负增长,剔除住房的核心服务通胀将逐步放缓。尽管市场对降息时…

易查分小程序丨查询下拉选择如何设置?

老师在发布查询时,如果查询条件过长或难以记忆,学生家长经常输入不正确,或难以记住,应该怎样解决? 可以使用【查询条件下拉选择功能】,让学生家长在下拉框中直接选择查询条件。 查询下拉演示效果 &#x1f…

宠物领养救助管理系带万字文档java项目基于springboot+vue的宠物管理系统java课程设计java毕业设计

文章目录 宠物领养救助管理系统一、项目演示二、项目介绍三、万字项目文档四、部分功能截图五、部分代码展示六、底部获取项目源码带万字文档(9.9¥带走) 宠物领养救助管理系统 一、项目演示 宠物领养救助系统 二、项目介绍 基于springbootv…

代码随想录——K 次取反后最大化的数组和(Leetcode1005)

题目链接 贪心 class Solution {public int largestSumAfterKNegations(int[] nums, int k) {int sum 0;Arrays.sort(nums);// 先把负数转正for(int i 0; i < nums.length; i){if(nums[i] < 0 && k > 0){nums[i] -nums[i];k--;}sum nums[i];}Arrays.so…