SwiftUI NavigatorStack 导航容器

NavigationStack 是一个用状态驱动、类型安全的声明式导航容器,它通过管理视图堆栈和导航路径来实现 SwiftUI 应用中的页面导航(专注于单栏场景)

NavigationStack 需要 iOS 16.0+以上版本支持。

核心要素

js 复制代码
NavigationStack (导航容器)
    │
    ├── 管理 NavigationPath (状态存储)
    │
    ├── 包含 navigationDestination (路由配置)
    │       │
    │       └── 判断数据类型 → 映射对应的视图
    │
    └── 视图层(导航的起点)

NavigationPath 是导航路径容器,用于管理 NavigationStack 的导航状态和历史记录
navigationDestination 是视图修饰符,用于定义数据类型到目标视图的映射关系,相当于导航系统的路由表

基本用法

1、简单页面跳转

可NavigationStack配合NavigationLink实现(不需要使用NavigationPath记录、管理路径)

js 复制代码
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类型并非只有一种:

js 复制代码
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

也可使用枚举管理多种数据类型:

js 复制代码
//注意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: 实现状态持久化和深度链接

js 复制代码
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 来跟踪导航状态
唯一性判断:用于比较两个实例是否代表相同的导航目标

js 复制代码
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 要求
js 复制代码
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 导航结构图:

js 复制代码
NavigationStack (容器层)
    │
    ├── 🏷️ NavigationBar (导航栏层)
    │       │
    │       ├── ◀️ [toolbar: .topBarLeading]
    │       │     ← 返回按钮 / 菜单按钮
    │       │
    │       ├── 🏷️ [navigationTitle]
    │       │     ← 页面标题 (居中显示)
    │       │
    │       └── ▶️ [toolbar: .topBarTrailing]
    │             ← 编辑按钮 / 更多操作
    │
    └── 📱 Content (内容层)
          ← 你的主要视图内容
          ← 可以独立滚动
js 复制代码
NavigationStack {                    // ← 根容器
    ContentView()                    // ← 📱 内容层
        .navigationTitle("标题")     // ← 🏷️ 中央标题
        .toolbar {
            ToolbarItem(placement: .topBarLeading) {  // ← ◀️ 左侧
                Button("返回") { ... }
            }
            ToolbarItem(placement: .topBarTrailing) { // ← ▶️ 右侧  
                Button("编辑") { ... }
            }
        }
}

自定义导航栏外观,参考案例:

js 复制代码
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)
    }
}
相关推荐
QuantumLeap丶7 小时前
《Flutter全栈开发实战指南:从零到高级》- 05 - 基础组件实战:构建登录界面
flutter·ios
黄毛火烧雪下7 小时前
(四)Flutter插件之IOS插件开发
flutter·ios
2501_915106327 小时前
iOS 混淆与 IPA 加固全流程,多工具组合实现无源码混淆、源码防护与可审计流水线(iOS 混淆|IPA 加固|无源码加固|App 防反编译)
android·ios·小程序·https·uni-app·iphone·webview
游戏开发爱好者87 小时前
用多工具组合把 iOS 混淆做成可复用的工程能力(iOS混淆 IPA加固 无源码混淆 Ipa Guard)
android·ios·小程序·https·uni-app·iphone·webview
2501_9159214310 小时前
掌握 iOS 26 App 性能监控,从监测到优化的多工具组合流程
android·macos·ios·小程序·uni-app·cocoa·iphone
2501_9160088911 小时前
手机 iOS 系统全解析,生态优势、开发机制与跨平台应用上架实践指南
android·ios·智能手机·小程序·uni-app·iphone·webview
I烟雨云渊T11 小时前
iOS原生与Flutter的交互编程
flutter·ios·交互
马拉萨的春天12 小时前
iOS的动态库和静态库的差异区别以及静态库的好处
macos·ios·cocoa
肖老师xy12 小时前
苹果(IOS)制作开发和发布证书
ios