NavigationStack 是 SwiftUI 中的一个现代导航容器,在 iOS 16+ 中引入,取代了 NavigationView。根据 Apple Developer Documentation, 2024,NavigationStack 通过类型安全的导航路径 (NavigationPath) 管理屏幕栈,支持深度导航、程序化返回到根屏幕以及在数据变更时保持状态。与 NavigationView 不同,NavigationStack 不需要额外的容器包装,并提供了直接绑定到导航路径的 Binding。
主要
NavigationStack 是一个容器 View,实现了基于栈 (LIFO) 的导航。它管理过渡历史,允许您将新屏幕放置到栈上,并通过系统“返回”按钮或程序化地返回。NavigationStack 是 SwiftUI 的一部分,从 iOS 16、iPadOS 16、macOS 13、watchOS 9 和 tvOS 16 开始。
NavigationStack 的主要创新是类型安全的导航路径。不是在创建 NavigationLink 时直接指定 destination,而是将一个值 (value) 放置到路径中,目标 View 的注册通过 .navigationDestination(for:destination:) 修饰器单独完成。这将导航和显示分离,使代码更加模块化和可测试。
根据 WWDC 2022 (Session 10054),NavigationStack 使用了一种新的基于 ObservableObject 和 SwiftUI 生命周期的导航机制。与依赖于底层 UINavigationController 的 NavigationView 不同,NavigationStack 完全在 SwiftUI 中实现,这提高了可预测性和与 SwiftUI 生命周期的兼容性。
NavigationStack 接受一个根 View 和一个可选的导航路径 (Binding 到 NavigationPath 或 Hashable 值的数组)。所有子屏幕通过 NavigationLink 或程序化地将值添加到路径来放置到栈中。
NavigationView 是 SwiftUI 的主要导航容器,一直使用到 iOS 16。它在底层自动管理 UINavigationController,这导致了一系列问题:数据变更时的行为不可预测、程序化导航困难以及缺乏类型安全性。
| 特征 | NavigationStack (iOS 16+) | NavigationView (iOS 13–15) |
|---|---|---|
| 导航类型 | 栈 (LIFO) | 栈 (LIFO) |
| 导航路径 | 有类型的 (NavigationPath) | 不支持 |
| Deep linking | 内置支持 | 需要未经正式的方法 |
| 程序化返回 | 通过路径 (pop, popToRoot) | dismiss, presentationMode |
| 底层 | SwiftUI 原生 | UINavigationController |
| 兼容性 | iOS 16+ | iOS 13+ |
关键优势 NavigationStack — 类型安全导航。您将路径定义为具体类型的数组 (或对于多种类型的栈使用 NavigationPath),并为每个类型注册 destination。这消除了类型不匹配错误,使导航可预测。
NavigationView 在 iOS 17 中已废弃。Apple 建议所有新项目以及在将最低版本更新到 iOS 16 时迁移到 NavigationStack。
NavigationPath 是一个类型,代表 NavigationStack 中的导航路径。它可以存储多种类型的值 (AnyHashable) 或通过 Binding 到数组 [T: Hashable] 与具体类型一起使用。NavigationPath 会自动编码和解码以保持状态。
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
HomeView()
.navigationDestination(for: String.self) { value in
DetailView(id: value)
}
.navigationDestination(for: Int.self) { value in
NumberView(number: value)
}
}
}
func goToRoot() {
path.removeLast(path.count)
}
func pushDeepLink() {
path.append("detail_42")
}
}
多种类型的栈: NavigationPath 可以包含不同类型的值,隻要它们实现 Hashable。例如,第一个屏幕可以接受 String (ID),第二个接受 Int (数字),第三个接受自定义的 enum Route。每种类型的显示都需要单独注册 .navigationDestination。
Codable 支持: 如果路径中的所有值都是 Codable + Hashable,NavigationPath 就实现 Codable。这可以在应用重启或切换到后台时保存和恢复导航状态。
.navigationDestination(for:destination:) — 一个修饰器,为特定数据类型注册目标 View。当 NavigationLink 将该类型的值放置到路径中时,SwiftUI 自动查找对应的 .navigationDestination 并创建屏幕。
enum AppRoute: Hashable {
case profile(UserID)
case settings
case about
}
struct AppNavigation: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
HomeView()
.navigationDestination(for: AppRoute.self) { route in
switch route {
case .profile(let id):
ProfileView(userId: id)
case .settings:
SettingsView()
case .about:
AboutView()
}
}
}
}
}
// Navigate: path.append(AppRoute.profile("user_123"))
重要规则: .navigationDestination 必须应用于位于 NavigationStack 内部的 View,并且在 NavigationLink 将值放置到路径之前。通常它被添加到根 View 或节的容器。如果没有找到值类型的 .navigationDestination,过渡将不会发生。
根据 SwiftUI Engineering (2023),.navigationDestination 可以在不同层级注册。SwiftUI 在尝试导航时会查找最近的 Destination。这可以在应用的不同部分覆盖某一类型的 destination。
模式 1:通过 enum Route 导航。 为应用的所有屏幕定义一个带关联值的 enum。使用一个 .navigationDestination 用于 AppRoute,使用 switch 进行路由。这为应用中所有可能的过渡提供了唯一的真实来源。
struct StoreView: View {
@State private var path: [ProductRoute] = []
var body: some View {
NavigationStack(path: $path) {
ProductGrid()
.navigationDestination(for: ProductRoute.self) { route in
switch route {
case .detail(let product):
ProductDetail(product: product)
case .reviews(let productId):
ReviewsView(productId: productId)
}
}
}
}
}
enum ProductRoute: Hashable {
case detail(Product)
case reviews(String)
}
模式 2:程序化导航和 deep linking。 NavigationStack 可以程序化地管理栈:添加、删除屏幕以及返回到根。这对于推送通知、深度链接以及重启后恢复导航是必不可少的。
模式 3:使用类型数组代替 NavigationPath。 如果所有屏幕都使用一种类型 (例如 String 或自定义的 enum),请使用 Binding 到 [T]。这提供了比 NavigationPath 更严格的类型化和更好的性能。NavigationPath 适合不同类型屏幕的多种栈。
根据 Point-Free (2024),使用 enum Route 的 NavigationStack 是组织 SwiftUI 应用中导航的首选方法。它使所有可能的过渡更加明确、类型安全和可测试,这对于有数十个屏幕的大型项目尤为重要。
常见问题
NavigationStack — 一个 SwiftUI 导航容器 (iOS 16+),通过类型安全的路径管理屏幕栈。它取代了 NavigationView,提供了 deep linking、程序化导航和状态保存的支持。
NavigationStack 使用 类型安全的路径 (NavigationPath) 代替直接将 NavigationLink 与 destination 连接。它支持程序化导航、deep linking 和 Codable 状态保存。它在 SwiftUI 上工作,而不是通过 UINavigationController。
NavigationPath 是一个表示栈中屏幕顺序的类型。您通过 path.append() 或使用 NavigationLink 带 value 将值添加到路径。每种类型的显示都需要注册 .navigationDestination。NavigationPath 支持 Codable 和自动状态保存。
通过 程序化路径管理:处理 URL 后,调用 path.append() 带上相应的路由值。NavigationStack 会自动显示目标屏幕。返回到根 — path.removeLast(path.count)。
是的,如果您的最低版本是 iOS 16+。NavigationView 在 iOS 17 中已废弃。迁移提供了 类型安全导航、deep linking 支持以及与 SwiftUI 生命周期的更好匹配。对于 iOS 15 及更低版本的项目,仍使用 NavigationView。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。