NavigationLink 是 SwiftUI 中的一个控件,用于在 NavigationStack 或 NavigationView 中切换到另一个屏幕。根据 Apple Developer Documentation, 2024,NavigationLink 创建一个按钮,点击后会将目标 View 放入导航堆栈中。在 iOS 16+ 中,建议使用带 value 和 NavigationDestination 的 NavigationLink,而不是直接使用 destination,以避免目标 View 的过早初始化。
要点
NavigationLink 是一个 View,点击后会触发导航跳转。在 NavigationStack 内部,点击 NavigationLink 会将目标屏幕放入堆栈并显示系统的 “返回” 按钮。NavigationLink 自 iOS 13 起就已存在,是 SwiftUI 中用户导航的主要方式。
NavigationLink 不继承自 UIButton — 它是一个 SwiftUI View,会自动适应上下文。在 List 内部,NavigationLink 会显示一个展开指示器(disclosure indicator)。在列表之外,NavigationLink 的行为类似于普通按钮,但具有导航行为。
根据 SwiftUI Lab (2024),NavigationLink 是 SwiftUI 应用程序中最常用的 View 之一,仅次于 Text、Image 和 VStack。理解初始化形式之间的差异对于性能和可预测的导航行为至关重要。
点击时,NavigationLink 将一个值(或 destination)添加到与最近的 NavigationStack 或 NavigationView 关联的导航堆栈中。SwiftUI 使用 EnvironmentValue 通过 View 层次结构传递导航路径。NavigationLink 从 Environment 中读取此路径,并在点击时修改它。
NavigationLink 有两种主要形式:带 destination(直接指定目标 View)和带 value(用于 NavigationDestination 的值)。形式的选择取决于 iOS 版本和导航架构。
| 形式 | 初始化器 | iOS 13–15 | iOS 16+ |
|---|---|---|---|
| Destination | NavigationLink(destination:label:) | 推荐 | 不推荐 |
| Value | NavigationLink(value:label:) | 不可用 | 推荐 |
| IsActive | NavigationLink(isActive:destination:label:) | 程序化导航 | 不推荐 |
Destination 形式 (iOS 13+): NavigationLink(destination: DetailView(), label: { Text(“Open”) }) 。这种形式在渲染 NavigationLink 时立即创建 DetailView,即使用户没有点击链接。这会导致 View 的过早初始化,如果目标 View 在初始化器中执行繁重操作,则可能导致潜在的性能问题。
Value 形式 (iOS 16+): NavigationLink(value: “detail_42”, label: { Text(“Open”) }) 。目标 View 仅在点击链接时创建,当 SwiftUI 找到相应的 .navigationDestination 时。这可以防止过早初始化,并使导航更加可预测。
NavigationLink 与 NavigationStack 在 iOS 16+ 中需要切换到 value 形式。您需要为导航定义数据类型(String、Int、enum Route),并通过 .navigationDestination 注册 destination。NavigationLink 只将值放入堆栈,SwiftUI 在点击时创建目标 View。
struct CatalogView: View {
let categories: [String]
var body: some View {
List(categories, id: \.self) { category in
NavigationLink(value: category) {
Text(category)
}
}
.navigationDestination(for: String.self) { category in
CategoryView(name: category)
}
}
}
// 程序化导航:
struct DeepLinkView: View {
@State private var path: [AppRoute] = []
var body: some View {
NavigationStack(path: $path) {
HomeView()
.navigationDestination(for: AppRoute.self) { route in
switch route {
case .detail(let id): DetailView(id: id)
case .settings: SettingsView()
}
}
.toolbar {
Button("打开设置") {
path.append(AppRoute.settings)
}
}
}
}
}
程序化导航: 向 path 添加值(通过 path.append)等同于点击具有相同值的 NavigationLink。这允许从 ViewModel、Coordinator 或响应推送通知时实现导航。
IsActive 形式 (NavigationLink(isActive:destination:label:)) 可用于兼容性,但在 iOS 16+ 中不推荐。请使用带 Binding 到路径数组或 NavigationPath 的 value 形式。
NavigationLink 在 List 中 会在行的右侧自动显示一个展开箭头(chevron),向用户指示点击将导航到另一个屏幕。List 自动管理箭头的显示 — 与列表外部的普通 NavigationLink 不同,后者没有箭头。
从 iOS 16 开始,List 中的 NavigationLink 在 List(data:rowContent:) 内部会自动使用 value 形式。在 List 内部使用 ForEach 时,展开箭头也会自动添加。这种行为无法通过修饰器关闭 — 只有将 NavigationLink 替换为 Button 才能移除箭头。
List 中 destination 形式的问题: 如果您在 List 内部使用 NavigationLink(destination:label:),所有目标 View 会在列表加载时立即创建,无论用户是否点击了链接。对于行数较多的列表,这可能会显著减慢初始加载速度并增加内存消耗。使用 NavigationStack 的 value 形式可以解决这个问题。
根据 WWDC 2022 (Session 10054),Apple 建议新项目使用 NavigationStack 和 NavigationLink 的 value 形式。这对于具有动态数据的 List 尤其重要,因为行数可能很多。
模式 1:自定义外观的 NavigationLink。 NavigationLink 接受任何 View 作为 label,允许为链接创建任意设计。在 List 内部,这特别方便 — 使用 NavigationLink 时会自动获得展开箭头。
NavigationLink(value: ProductRoute.detail(product)) {
HStack {
AsyncImage(url: product.imageURL)
.frame(width: 60, height: 60)
VStack(alignment: .leading) {
Text(product.name).font(.headline)
Text(product.price) .foregroundColor(.secondary)
}
}
.padding(8)
}
模式 2:不带箭头的 NavigationLink(自定义按钮)。 如果您不需要展开箭头,请使用 Button 进行程序化导航:path.append(value)。这对于 NavigationLink 看起来不自然的自定义界面元素很有用。
模式 3:条件导航。 您可以通过使用空的 destination 或不添加特定值的 .navigationDestination 来阻止 NavigationLink。通过 path 进行程序化导航允许在添加值之前检查条件。
根据 Hacking with Swift (2024),大多数 NavigationLink 问题与在旧项目中使用 destination 形式有关。在迁移到 NavigationStack 时,将所有 NavigationLink(destination:label:) 替换为 NavigationLink(value:label:),并在根级别添加 .navigationDestination。
常见问题
NavigationLink 是 SwiftUI 中用于切换到另一个屏幕的 View。点击时,将目标屏幕放入 NavigationStack 或 NavigationView 的导航堆栈中。支持两种形式:带 destination(目标 View)和带 value(用于路由的值)。
Value 形式 (iOS 16+) 更优:目标 View 仅在点击时创建,而不是在链接渲染时创建。Destination 形式会立即创建 View,这可能会导致性能问题。对于 iOS 16+ 的项目,请使用 value + NavigationDestination。
SwiftUI 会自动向 List 内部的 NavigationLink 添加 disclosure indicator(箭头),指示跳转的可能性。此行为无法禁用。如果不需要箭头,请使用 Button 通过 path.append() 进行程序化导航。
使用带 Binding 路径的 NavigationStack,并通过 path.append(value) 添加值。这等同于点击具有相同 value 的 NavigationLink。程序化导航允许实现 Deeplink、推送通知和 Coordinator 模式。
如果目标 View 在初始化器中执行 繁重操作,Destination 形式可能会产生影响 — 所有 destination 在列表渲染时都会创建。使用 NavigationStack 的 Value 形式通过仅在点击时创建 View 来解决这个问题。对于 50 行以上的列表,差异显著。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。