SwiftUI 导航 cookbook
The SwiftUI cookbook for navigation
2022年6月6日
一句话判断
NavigationStack 和 NavigationSplitView 终于让 SwiftUI 的导航系统有了数据驱动和编程控制的能力——如果你还在用老的 NavigationLink API,该升级了。
这场 Session 讲了什么
SwiftUI 引入了全新的数据驱动导航 API,Session 以”烹饪食谱应用”为示例,逐步展示了三种导航结构的实现:
NavigationStack:单列 push-pop 导航,类似 iOS Settings。核心是一个 path 绑定——一个集合类型,代表当前栈上所有已推送的数据值。修改 path 就能实现深链接和编程式导航。
NavigationSplitView:多列导航,类似 Mail 或 Notes。支持两列和三列两种配置,在 iPhone 和 Slide Over 上自动退化为单列堆栈。
NavigationLink 的新变体:不再直接绑定 View,而是绑定一个值(value)。配合 navigationDestination 修饰符声明值到视图的映射关系。
整个系统的工作原理:当用户点击 NavigationLink 时,值被追加到 path;NavigationStack 根据 path 中的值和已注册的 destination 修饰符,决定推送哪个视图。
值得深挖的点
Path 绑定的编程式控制是这次更新最大的卖点。过去要做深链接,你需要为每个 NavigationLink 维护独立的 binding。现在 path 就是一个普通数组——要跳转到特定页面,直接设置 path 内容;要回到根视图,清空 path 即可。这让 URL 路由和状态恢复变得异常简单。
navigationDestination 的类型分发机制。一个 navigationDestination(for: Recipe.self) 修饰符会处理所有类型为 Recipe 的值。如果你需要在一个栈中展示多种类型,可以注册多个 destination 修饰符,SwiftUI 根据值的类型自动路由。
NavigationSplitView 的自适应行为值得理解。在 iPad 上它是三列布局(sidebar / content / detail),在 iPhone 上自动变成单列堆栈。这种自适应不需要你写任何条件代码,SwiftUI 根据屏幕尺寸自动处理。
代码片段
基础 NavigationStack:
struct RecipeListView: View {
// path 是一个数组,存储所有已推送的 Recipe
@State var path = [Recipe]()
var body: some View {
NavigationStack(path: $path) {
List(categories) { category in
Section(category.name) {
ForEach(category.recipes) { recipe in
// 新版 NavigationLink 只绑定值
NavigationLink(recipe.name, value: recipe)
}
}
}
.navigationTitle("食谱")
// 声明值到视图的映射
.navigationDestination(for: Recipe.self) { recipe in
RecipeDetailView(recipe: recipe)
}
}
}
}
编程式导航和深链接:
// 深链接:直接设置 path
func deepLink(to recipe: Recipe) {
path = [recipe]
}
// 回到根视图
func popToRoot() {
path.removeAll()
}
// 回退一步
func popOne() {
path.removeLast()
}
三列 NavigationSplitView:
NavigationSplitView {
// 第一列:分类列表
List(categories, selection: $selectedCategory)
} content: {
// 第二列:食谱列表
List(selectedCategory?.recipes ?? []) { recipe in
NavigationLink(recipe.name, value: recipe)
}
} detail: {
// 第三列:食谱详情
if let recipe = selectedRecipe {
RecipeDetailView(recipe: recipe)
}
}
最佳实践
- 用新变体 NavigationLink(value:):不要在 Link 中直接嵌入目标视图,分离数据和视图
- Path 类型用具体类型:单类型栈用
[Recipe],多类型栈用NavigationPath - destination 修饰符放在合适位置:放在栈内或根视图上,确保所有层级都能路由
- NavigationSplitView 配置用修饰符:columnVisibility 和 columnWidths 都有对应的修饰符
- 持久化 path 用 Codable:NavigationPath 支持 codable 转换,可以保存和恢复导航状态
还有什么值得关注
- NavigationSplitView 的列配置选项非常丰富,建议看 “SwiftUI on iPad: Organize your interface” 了解详情
- 旧的 NavigationLink(view:) API 继续可用,但新的值绑定 API 是未来方向
- NavigationPath 是类型擦除的路径容器,适合需要混合多种数据类型的复杂导航场景
- Session 的 cookbook 隐喻贯穿全场,代码示例都是围绕食谱应用展开的