SwiftUI|一种自定义 macOS TabView 的方案
介绍一种在 SwiftUI 里实现自定义 TabView 的方法(macOS)
目前在 macOS 上,SwiftUI 的 TabView 是利用 AppKit 的 NSTabView 实现的。NSTabView 的默认样式带有边框和背景色,顶部 tab 切换使用 NSSegmentedControl 实现,无法满足特定设计需求。
网上有一些利用 SwiftUI 基础视图自己实现 tabbar 的方案。这些方案本质上不依赖 TabView 或 NSTabView 实现,只是做了一个模拟切换 tab 的视图,然后在 tab 切换时利用 SwiftUI 的渲染特性,在 body 里面返回对应 tab 的 View。这种方案有一个缺点——每次回到某个 tab 时,对应的 View 会被重新创建。如果页面结构简单的话,这种方案可取;但是对于复杂的页面,每次重新创建的性能开销还是很大的,就不能采用这种方案了。
所以自定义 TabView 最好还是基于原生的 TabView/NSTabView 组件实现。原生组件是不会在切到某个 tab 时重新创建对应的 View 或页面的。
在 AppKit/Cocoa 时代,大家也有这种自定义 TabView 的需求。当时有前辈给出了一种方案:通过修改 NSTabView 的 tabViewType
隐藏它的 tabbar 和边框;然后在 NSTabView 上方添加一个自定义的切换 tab 的视图即可。这个方案在 SwiftUI 上也可以使用,也是本篇文章要介绍的方案。
将 NSTabView 的 tabViewType
设置为 noTabsNoBorder
,可以隐藏 NSTabView 的 tabbar 和边框。目前 SwiftUI 的 TabView 还不支持直接修改 tabViewType
,我们需要借助 SwiftUIIntrospect
来间接获取到 TabView 底层使用的 NSTabView。
TabView(selection: $selectedIndex,
content: {
//...
})
.tabViewStyle(.automatic)
.backgroundStyle(.clear)
.introspect(.tabView, on: .macOS(.v14, .v15)) { tabView in
tabView.tabViewType = .noTabsNoBorder
}
然后在 TabView 上方添加一个自定义的 tab 切换视图。这个自定义视图可以是利用 SwiftUI 基础视图自己实现的 tabbar,也可以图省事直接用 Picker(segmented 样式的)。
Picker("", selection: $selectedIndex) {
ForEach(tabNames.indices, id: \.self) { nameIndex in
Text(tabNames[nameIndex])
}
}
.pickerStyle(.segmented)
定义一个 Int 类型的状态变量连接 TabView 和 Picker。
@State private var selectedIndex = 0
这样切换 Picker 选项时,TabView 也会切换到对应的页面。