SwiftUI 入门之 NavigationSplitView 应用
SwiftUI 于 2019 年度 WWDC 全球开发者大会上发布,它是基于 Swift 建立的声明式框架。初识 SwiftUI 感觉很是怪异,完全依靠编码实现用户界面,没有 Storyboard 所见即所得来的舒适。但真的使用过后,发现 SwiftUI 这种描述式的构建方式非常简洁(Swift 相比于 Objective-C 也更加简洁明了,两者可谓相辅相成),代码量会减少很多。不过 Xcode 上界面预览的支持有点差,很多时候还需要调试后查看编写效果。
NavigationSplitView 基本应用
在 macOS 上使用 SwiftUI 开发应用,SplitView 可能是最常使用的布局模式之一,大量的系统原生应用(如设置、邮件等)均采用了这种布局方式。NavigationSplitView 是非常方便使用的显示两到三列视图的容器,代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| NavigationSplitView { } detail: { }
NavigationSplitView { } content: { } detail: { }
|
NavigationSplitView 应用示例
NavigationSplitView 一般会搭配 List
使用,以渲染列表视图。如下实现了一个简单的两列视图。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import SwiftUI
struct EmployeeModel { var name: String var details: String }
struct ContentView: View { @State var selectedIndex = 0 let model = [EmployeeModel(name: "ZhangSan", details: "C/C++, Swift"), EmployeeModel(name: "LiSi", details: "macOS Kernel, objc"), EmployeeModel(name: "WangWu", details: "Linux, C++")] var body: some View { NavigationSplitView { List(0 ..< model.count, id: \.self, selection: $selectedIndex) { index in Text(model[index].name) } } detail: { Text(model[selectedIndex].details) }
} }
#Preview { ContentView() }
|
首先定义了数据结构为结构体数组,然后使用 List
遍历该数组显示所有项目。这里 id: \.self
表示使用变量自身作为列表元素的标识符,以便于系统正确管理列表数据。另外使用 @State
声明了一个表示用户选择索引的变量 selectedIndex
,并在使用 List 遍历时将其绑定到当前列表。这样每次用户选择列表某一行,将触发 selectedIndex
更新,进而重新渲染视图。@State
是一种属性包装器,这里不做具体解释。
NavigationSplitView 嵌套 Section
有些时候可能会有将相关联的项目分组显示的场景,那么可以在 List
中使用 Section
或 Group
等容器,而不要嵌套使用 List
。那么这里将代码改动一下,使用 Section
进行分组显示,并使用 ForEach
将每一个元素插入列表。详细视图这里暂不处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| struct ContentView: View { @State var selectedIndex = 0 let model = [[EmployeeModel(name: "ZhangSan", details: "C/C++, Swift"), EmployeeModel(name: "LiSi", details: "macOS Kernel, objc"), EmployeeModel(name: "WangWu", details: "Linux, C++")], [EmployeeModel(name: "SuSan", details: "C/C++, Swift"), EmployeeModel(name: "LiSi", details: "macOS Kernel, objc"), EmployeeModel(name: "WangWu", details: "Linux, C++")]] var body: some View { NavigationSplitView { List(0 ..< model.count, id: \.self, selection: $selectedIndex) { index in Section("Section \(index)") { ForEach(0 ..< model[index].count, id: \.self) { i in Text(model[index][i].name) } } } } detail: {
}
} }
|
奇怪的现象发生了,如下图,当我们选择某个组的其中一项时,会发现其他组的同位置项也被选中了。这是因为系统将不同 Section
中的同位置项目当成了一个,毕竟我们要求系统以变量自身作为列表元素的标识符,这里系统可能是以元素在 Section
中的顺序作为标识符。
修复该问题也很简单,自定义不会重复的标识符即可。可以显式指定 Text
的 tag
,使得标识不会重复,这样用户选择列表元素时就不会单击选中多个。需要注意的是,这里的 tag
就是 selectedIndex
,所以在详细视图处理时需要将其转化为正确的索引量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| struct ContentView: View { @State var selectedIndex = 0 let model = [[EmployeeModel(name: "ZhangSan", details: "C/C++, Swift"), EmployeeModel(name: "LiSi", details: "macOS Kernel, objc"), EmployeeModel(name: "WangWu", details: "Linux, C++")], [EmployeeModel(name: "SuSan", details: "C/C++, Swift"), EmployeeModel(name: "LiSi", details: "macOS Kernel, objc"), EmployeeModel(name: "WangWu", details: "Linux, C++")]] var body: some View { NavigationSplitView { List(0 ..< model.count, id: \.self, selection: $selectedIndex) { index in Section("Section \(index)") { ForEach(0 ..< model[index].count, id: \.self) { i in let tag = index == 0 ? i : model[index].count + i Text(model[index][i].name) .tag(tag) } } } } detail: { let sectionIndex = selectedIndex >= model[0].count ? 1 : 0 let itemIndex = sectionIndex == 0 ? selectedIndex : selectedIndex - model[0].count Text(model[sectionIndex][itemIndex].details) }
} }
|