SwiftUI 2022 大更新:NavigationStack、Swift Charts、Layout 协议全来了
What's new in SwiftUI
2022年6月6日
一句话判断
这是 SwiftUI 历史上更新最大的一年——NavigationStack/SplitView 终于给出了数据驱动的导航方案,Swift Charts 填补了数据可视化的空白,Layout 协议让你拥有了和 Apple 同等的布局能力。
这场 Session 讲了什么
SwiftUI 今年在四个方向上有重大更新。
导航方面,新增了 NavigationStack 和 NavigationSplitView 两个容器。NavigationStack 支持 push/pop 风格导航,配合 value-based NavigationLink 和 navigationDestination modifier 实现数据驱动的导航路径管理。你可以把路径表示为一个数组,直接操作它来做「返回首页」这类操作。NavigationSplitView 支持两栏和三栏布局,在紧凑尺寸类下自动折叠成 stack。两者可以直接组合。
Swift Charts 是全新的声明式图表框架。用 Chart、BarMark、LineMark、RuleMark 等标记类型构建图表,支持 foregroundStyle、symbol 等 modifier,自动处理本地化、Dark Mode 和 Dynamic Type。几行代码就能生成专业级图表。
控件和交互方面,Form 有了新的 grouped style(类似 macOS Ventura 的 System Settings),LabeledContent 视图让对齐标签和值变得简单。TextField 支持 axis 参数实现多行输入,MultiDatePicker 支持非连续日期选择。Toggle 和 Picker 支持 mixed state(多选时的混合状态显示)。Table 在 iPadOS 上可用。新增了 PhotosPicker、ShareLink 和基于 Transferable 协议的拖拽。
图形方面,Color.gradient 添加了微妙渐变,ShapeStyle.shadow modifier 给文字和符号添加精细阴影。Layout 协议和 Grid 容器给了你完全的自定义布局能力。AnyLayout 让你在不同布局间动画切换。
值得深挖的点
NavigationStack 的数据驱动模型终于对齐了 UIKit 的 NavigationController。 之前的 NavigationView 有太多痛点:深层链接难做、路径状态不可观测、programmatic 控制不直观。现在的 value-based NavigationLink + navigationDestination 模型把导航路径变成了纯粹的数据操作。你的 path 数组就是导航栈的完整状态——做 deep link、保存/恢复状态、返回特定页面都变成了数组操作。加上 NavigationSplitView 在不同设备上的自动适配,一个声明就能覆盖 iPhone 的 stack 导航和 iPad/Mac 的 split view。
Layout 协议是 SwiftUI 布局系统的终极形态。 之前如果你想实现 SwiftUI 内置布局做不到的自定义布局,只能用 GeometryReader 和 offset 做各种 hack。Layout 协议给你了和 Apple 实现 HStack、VStack 完全相同的能力:sizeThatFits 计算自身大小,placeSubviews 放置子视图,支持 cache 和动画。配合 layoutValue 在子视图上附加元数据,你可以在 layout 中读取这些值来做条件布局。Session 里的 seating chart demo 展示了一个同时支持 pod 和 row 两种排列方式的复杂布局,用 AnyLayout 切换并带动画。
代码片段
数据驱动的 NavigationStack:
// 用 value-based NavigationLink,路径是 [FoodItem] 数组
@State private var path: [FoodItem] = []
NavigationStack(path: $path) {
List(foodItems) { item in
NavigationLink(value: item) { // 传入数据而非 view
FoodRow(food: item)
}
}
.navigationDestination(for: FoodItem.self) { item in
FoodDetailView(item: item, path: $path)
}
}
// 返回第一项:直接操作数据
Button("Back to First") {
path.removeSubrange(1...)
}
Swift Charts 几行代码生成图表:
import Charts
Chart(tasksRemaining) { task in
LineMark(
x: .value("Date", task.date, unit: .day),
y: .value("Remaining", task.count)
)
.foregroundStyle(by: .value("Category", task.category))
.symbol(by: .value("Category", task.category))
}
// 自动处理坐标轴、颜色、图例、Dark Mode、Dynamic Type
可自定义的 sheet 高度和 Window 场景:
// 可缩放的 sheet
.sheet(isPresented: $showBudget) {
BudgetView()
.presentationDetents([.height(250), .medium])
.presentationDragIndicator(.visible)
}
// 单例窗口(macOS)
Window("Party Budget", id: "budget") {
BudgetView()
}
.keyboardShortcut("0")
.defaultPosition(.topLeading)
.defaultSize(width: 220, height: 250)
// 菜单栏 app
MenuBarExtra("Bulletin", systemImage: "quote.bubble") {
BulletinBoard()
}
.menuBarExtraStyle(.window) // 或 .menu
最佳实践
- 导航迁移:用 NavigationStack 替代 NavigationView 的 stack 用法,用 NavigationSplitView 替代 column 用法。
- 优先使用 value-based NavigationLink 而非 view-based,让导航状态可观测可持久化。
- 用
.formStyle(.grouped)在 macOS 上获得类似 System Settings 的外观。 - PhotosPicker + ShareLink + dropDestination 组合覆盖了图片选取、分享和拖拽的完整工作流。
- 自定义布局时用 Layout 协议而非 GeometryReader hack,前者支持动画和 preview。
还有什么值得关注
- Text 现在支持在 weight、style 甚至 layout 之间做动画。
- Xcode 14 的 Preview 默认运行在 live mode,preview variants 让你同时看不同 appearance 和 type size。
- Stepper 在 macOS 上支持 format 参数显示可编辑的值字段,watchOS 也支持 Stepper 了。
- Search 支持 tokens、suggestions 和 search scopes。