NavigationStack 是一个用状态驱动、类型安全的声明式导航容器,它通过管理视图堆栈和导航路径来实现 SwiftUI 应用中的页面导航(专注于单栏场景)
NavigationStack 需要 iOS 16.0+以上版本支持。
核心要素
NavigationStack (导航容器) │ ├── 管理 NavigationPath (状态存储) │ ├── 包含 navigationDestination (路由配置) │ │ │ └── 判断数据类型 → 映射对应的视图 │ └── 视图层(导航的起点)
NavigationPath 是导航路径容器,用于管理 NavigationStack 的导航状态和历史记录
navigationDestination 是视图修饰符,用于定义数据类型到目标视图的映射关系,相当于导航系统的路由表
基本用法
1、简单页面跳转
可NavigationStack配合NavigationLink实现(不需要使用NavigationPath记录、管理路径)
struct ContentView: View { var body: some View { NavigationStack { List { NavigationLink("前往详情页", value: "详情内容") NavigationLink("设置", value: "设置页面") } .navigationDestination(for: String.self) { value in DetailView(content: value) } } } } struct DetailView: View { let content: String var body: some View { Text("详情: (content)") .navigationTitle("详情页") } }
2、简单页面跳转:多类型路由映射
很多时候,navigationDestination映射的value类型并非只有一种:
struct Test: View { var body: some View { NavigationStack { List { // 使用 value 参数 - 必须遵循 Hashable(swift值类型数据默认遵循Hashable协议) NavigationLink("使用 value", value: "字符串值") NavigationLink("使用数字", value: 42) } .navigationDestination(for: String.self) { value in Text("字符串值: (value)") } .navigationDestination(for: Int.self) { value in Text("整数值: (value)") } } } }
3、简单页面跳转:多类型路由映射2
也可使用枚举管理多种数据类型:
//注意value类型,需要遵循Hashable enum Route: Hashable { case product(Int) case profile(String) case settings } struct MultiTypeNavigationView: View { var body: some View { NavigationStack { VStack(spacing: 20) { NavigationLink("产品详情", value: Route.product(123)) NavigationLink("用户资料", value: Route.profile("张三")) NavigationLink("设置", value: Route.settings) } .navigationDestination(for: Route.self) { route in switch route { case .product(let id): ProductDetailView(productId: id) case .profile(let username): ProfileView(username: username) case .settings: SettingsView() } } } } }
4、多层页面跳转
可使用NavigationStack、NavigationPath实现
NavigationPath提供了以下方法用于管理路径:
append() : 跳转到新页面
removeLast(): 返回上一页
removeLast(n): 返回前n页
removeAll(): 返回首页
count: 显示当前导航深度
Codable: 实现状态持久化和深度链接
import SwiftUI // 定义路由枚举 enum Route: Hashable { case detail(String) case settings case profile(Int) } struct ContentView: View { @State private var path = NavigationPath() var body: some View { NavigationStack(path: $path) { List { Button("跳转到详情页") { path.append(Route.detail("Hello World")) } Button("跳转到设置") { path.append(Route.settings) } Button("跳转到用户资料") { path.append(Route.profile(123)) } Button("多层级跳转") { path.append(Route.detail("第一层")) path.append(Route.settings) path.append(Route.profile(456)) } } .navigationTitle("首页") .navigationDestination(for: Route.self) { route in switch route { case .detail(let text): DetailView(text: text, path: $path) case .settings: SettingsView(path: $path) case .profile(let userId): ProfileView(userId: userId, path: $path) } } } } } struct DetailView: View { let text: String @Binding var path: NavigationPath var body: some View { VStack { Text("详情页: (text)") .font(.title) Button("前往下一层") { path.append(Route.detail("从详情页跳转")) } Button("返回首页") { path.removeLast(path.count) } Button("返回上一层") { path.removeLast() } } } } struct SettingsView: View { @Binding var path: NavigationPath var body: some View { VStack { Text("设置页面") .font(.title) Button("返回") { path.removeLast() } } } } struct ProfileView: View { let userId: Int @Binding var path: NavigationPath var body: some View { VStack { Text("用户资料: (userId)") .font(.title) Button("跳转到详情") { path.append(Route.detail("来自用户资料")) } } } }
5、Hashable 的作用
导航必需:NavigationLink 的 value 参数必须遵循 Hashable
路径管理:NavigationPath 依赖 Hashable 来跟踪导航状态
唯一性判断:用于比较两个实例是否代表相同的导航目标
5.1、NavigationLink 的 Hashable 要求
struct NavigationLinkRequirement: View { var body: some View { NavigationStack { List { // ✅ 形式1:使用 value 参数 - 必须遵循 Hashable NavigationLink("使用 value", value: "字符串值") NavigationLink("使用数字", value: 42) // ❌ 这会导致编译错误,因为 MyData 不遵循 Hashable // NavigationLink("无效", value: MyData()) // ✅ 形式2:使用 destination 参数 - 不需要 Hashable NavigationLink("使用 destination") { MyCustomView() } // ✅ 传统形式 - 不需要 Hashable NavigationLink("传统形式", destination: Text("目标视图")) } .navigationDestination(for: String.self) { value in Text("字符串值: (value)") } .navigationDestination(for: Int.self) { value in Text("整数值: (value)") } } } } // ❌ 不遵循 Hashable 的类型 struct MyData { let content: String } struct MyCustomView: View { var body: some View { Text("自定义视图") } }
5.2、NavigationPath 的 Hashable 要求
struct NavigationPathExample: View { @State private var path = NavigationPath() var body: some View { NavigationStack(path: $path) { VStack(spacing: 20) { Text("NavigationPath 演示") .font(.title) Button("添加字符串") { // ✅ 字符串遵循 Hashable path.append("新页面") } Button("添加整数") { // ✅ 整数遵循 Hashable path.append(100) } Button("添加自定义类型") { // ✅ 只要遵循 Hashable 就可以 path.append(MyHashableData(name: "测试", id: 1)) } // ❌ 这会导致运行时错误 Button("添加非 Hashable 类型(会崩溃)") { // path.append(MyData()) // 取消注释会崩溃 } Button("查看路径深度: (path.count)") { print("当前路径深度: (path.count)") } Button("返回") { if !path.isEmpty { path.removeLast() } } Button("返回根视图") { path = NavigationPath() // 重置路径 } } .navigationDestination(for: String.self) { value in Text("字符串页面: (value)") } .navigationDestination(for: Int.self) { value in Text("整数页面: (value)") } .navigationDestination(for: MyHashableData.self) { data in Text("自定义数据: (data.name) - (data.id)") } } } } // ✅ 遵循 Hashable 的自定义类型 struct MyHashableData: Hashable { let name: String let id: Int }
6、导航栏UI自定义
NavigationStack 导航结构图:
NavigationStack (容器层) │ ├── 🏷️ NavigationBar (导航栏层) │ │ │ ├── ◀️ [toolbar: .topBarLeading] │ │ ← 返回按钮 / 菜单按钮 │ │ │ ├── 🏷️ [navigationTitle] │ │ ← 页面标题 (居中显示) │ │ │ └── ▶️ [toolbar: .topBarTrailing] │ ← 编辑按钮 / 更多操作 │ └── 📱 Content (内容层) ← 你的主要视图内容 ← 可以独立滚动
NavigationStack { // ← 根容器 ContentView() // ← 📱 内容层 .navigationTitle("标题") // ← 🏷️ 中央标题 .toolbar { ToolbarItem(placement: .topBarLeading) { // ← ◀️ 左侧 Button("返回") { ... } } ToolbarItem(placement: .topBarTrailing) { // ← ▶️ 右侧 Button("编辑") { ... } } } }
自定义导航栏外观,参考案例:
struct CustomNavigationView: View { var body: some View { NavigationStack { List { NavigationLink("标准页面", value: "standard") NavigationLink("自定义标题页面", value: "custom") NavigationLink("隐藏导航栏页面", value: "hidden") } .navigationDestination(for: String.self) { value in switch value { case "standard": StandardView() case "custom": CustomTitleView() case "hidden": HiddenNavBarView() default: Text("未知页面") } } .navigationTitle("导航演示") .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("设置") { print("设置按钮点击") } } } } .tint(.purple) // 统一色调 } } struct StandardView: View { var body: some View { Text("标准页面") .navigationTitle("标准标题") .navigationBarTitleDisplayMode(.automatic) } } struct CustomTitleView: View { var body: some View { VStack { Text("自定义标题页面") // 自定义标题视图 Color.blue .frame(height: 200) .overlay( Text("大标题") .font(.largeTitle) .foregroundColor(.white) ) } .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .principal) { VStack { Text("自定义标题") .font(.headline) Text("副标题") .font(.subheadline) .foregroundColor(.secondary) } } } } } struct HiddenNavBarView: View { var body: some View { Text("隐藏导航栏页面") .navigationBarBackButtonHidden(true) .navigationBarHidden(true) .toolbar(.hidden, for: .navigationBar) } }