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

概览

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

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

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

  • 概览
  • [1. iPadOS 18 中全新的 Sidebar+悬浮 TabView 样式](#1. iPadOS 18 中全新的 Sidebar+悬浮 TabView 样式)
  • [2. 新 TabView 样式在 iOS 18 中的表现](#2. 新 TabView 样式在 iOS 18 中的表现)
  • [3. SwiftUI 6.0 新的 Tab 和 TabSection 原生视图](#3. SwiftUI 6.0 新的 Tab 和 TabSection 原生视图)
  • [4. 让 Tab 懂你所选](#4. 让 Tab 懂你所选)
  • [5. 调整 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 外观的:

swift 复制代码
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 视图来描述每个标签页:

swift 复制代码
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 上即可:

swift 复制代码
TabView {
	...
}
.tabViewStyle(.sidebarAdaptable)

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

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


4. 让 Tab 懂你所选

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

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

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

swift 复制代码
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) 修改器方法:

swift 复制代码
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 样式以及其它有趣的新特性。

感谢观赏,再会!😎

相关推荐
大熊猫侯佩22 天前
SwiftUI 6.0(iOS 18)新增的网格渐变色 MeshGradient 解惑
动画·颜色·ios 18·swiftui 6.0·渐变色·gradient·网格渐变色
大熊猫侯佩24 天前
SwiftUI 6.0(iOS 18)自定义容器值(Container Values)让容器布局渐入佳境(上)
foreach·group·layout·ios 18·swiftui 6.0·containervalues·自定义容器
大熊猫侯佩4 个月前
SwiftUI 6.0(Xcode 16)新 PreviewModifier 协议让预览调试如虎添翼
ios 18·xcode 16·swiftui 6.0·previewmodifier·预览调试·inject 注入测试数据·mock data
大熊猫侯佩4 个月前
SwiftUI 6.0(iOS 18.0)滚动视图新增的滚动阶段(Scroll Phase)监听功能趣谈
scrollview·ios 18·xcode 16·swiftui 6.0·滚动视图·scroll phase·监听滚动状态
大熊猫侯佩4 个月前
SwiftUI 6.0(iOS 18/macOS 15)关于颜色 Color 的新玩法
color·ios 18·xcode 16·swiftui 6.0·macos 15·混合颜色·渐变动画
飞鸟真人4 个月前
使用supportFragmentManager管理多个fragment切换
android·tab·fragment·fragmentmanager
大熊猫侯佩4 个月前
SwiftUI 6.0(iOS 18)ScrollView 全新的滚动位置(ScrollPosition)揭秘
scrollview·ios 18·swiftui 6.0·ipados 18·滚动视图·wwdc24·scrollposition
大熊猫侯佩5 个月前
SwiftUI 6.0(Xcode 16)全新 @Entry 和 @Previewable 宏让开发妙趣横生
·entry·ios 18·xcode 16·swiftui 6.0·previewable·wwdc 24
喜好儿aigc5 个月前
WWDC 2024:苹果将在 iOS 18 中对 Siri 进行人工智能升级,集合多项人工智能功能
人工智能·ios·wwdc·苹果·ios 18