当前位置: 首页 > news >正文

SwiftUI NavigatorStack 导航容器

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 inDetailView(content: value)}}}
}struct DetailView: View {let content: Stringvar 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 inText("字符串值: \(value)")}.navigationDestination(for: Int.self) { value inText("整数值: \(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 inswitch 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 settingscase 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 inswitch 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: NavigationPathvar 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: NavigationPathvar body: some View {VStack {Text("设置页面").font(.title)Button("返回") {path.removeLast()}}}
}struct ProfileView: View {let userId: Int@Binding var path: NavigationPathvar body: some View {VStack {Text("用户资料: \(userId)").font(.title)Button("跳转到详情") {path.append(Route.detail("来自用户资料"))}}}
}

5、Hashable 的作用

导航必需:NavigationLink 的 value 参数必须遵循 Hashable

路径管理:NavigationPath 依赖 Hashable 来跟踪导航状态

唯一性判断:用于比较两个实例是否代表相同的导航目标

struct NavigationLinkRequirement: View {var body: some View {NavigationStack {List {// ✅ 形式1:使用 value 参数 - 必须遵循 HashableNavigationLink("使用 value", value: "字符串值")NavigationLink("使用数字", value: 42)// ❌ 这会导致编译错误,因为 MyData 不遵循 Hashable// NavigationLink("无效", value: MyData())// ✅ 形式2:使用 destination 参数 - 不需要 HashableNavigationLink("使用 destination") {MyCustomView()}// ✅ 传统形式 - 不需要 HashableNavigationLink("传统形式", destination: Text("目标视图"))}.navigationDestination(for: String.self) { value inText("字符串值: \(value)")}.navigationDestination(for: Int.self) { value inText("整数值: \(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("添加字符串") {// ✅ 字符串遵循 Hashablepath.append("新页面")}Button("添加整数") {// ✅ 整数遵循 Hashablepath.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 inText("字符串页面: \(value)")}.navigationDestination(for: Int.self) { value inText("整数页面: \(value)")}.navigationDestination(for: MyHashableData.self) { data inText("自定义数据: \(data.name) - \(data.id)")}}}
}// ✅ 遵循 Hashable 的自定义类型
struct MyHashableData: Hashable {let name: Stringlet 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 inswitch 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)}
}
http://www.jsqmd.com/news/20677/

相关文章:

  • 深入解析:【仿生机器人】基于 GPT-SoVITS 的 发声器
  • .NET Core报错克服【无废话上操作】
  • 深入解析:【算法】【数学】 练习题目列表
  • 题解:P11831 [省选联考 2025] 追忆
  • 2025-10-23 MX-S 模拟赛 赛后总结【MX】
  • PCL1.12 解决memory.h中EIGEN处中断问题
  • 深入解析:链表的核心思想
  • AI元人文构想:参与“自由与责任”哲学思考——岐金兰之实验
  • 20251023
  • Java常用机制 - SPI机制详解
  • 实用指南:用户研究:用户研究和数据分析的根本联系与区别
  • 2025.10.23——2绿2蓝
  • Anaconda命令大全conda
  • 完整教程:状态管理库 Zustand 的接入流程与注意点
  • 采用opencv来识别信用卡的号码
  • 塔吊施工环境与附属设施监测!思通数科 AI 卫士筑牢全场景安全防线
  • 精读《C++20设计模式》:重新理解设计模式系列 - 详解
  • 网络设备
  • Kafka-保证消息消费的顺序性及高可用机制 - 教程
  • 第二十二篇
  • CSharp: Convert CSV to XLS Using Open XML SDK
  • 实用指南:PyTorch 数据处理工具箱:从数据加载到可视化的完整指南
  • 《程序员修炼之道:从小工到专家》阅读笔记1
  • 多级多卡训练模型时有些参数没有参与loss计算和梯度更新的解决办法
  • 负载均衡及三种软件负载
  • 在 GEO / AIO 角度:如何优化 SEO 内容?
  • Android Handler的runWithScissors手段
  • Idea提高制作效率的快捷键最佳学习方式
  • Elasticsearch8容器化部署 - 实践
  • ski 和 db 模块的通信