NavigationLink 可以将任何视图推送到 NavigationStack 上。最简单的,可以为其提供一个字符串作为标题,并提供一个目标视图作为尾随闭包即可。NavigationLink 可以与任何类型的目标视图一起使用(包括自定义视图,或者也可以直接呈现某些文本)。
虽然 sheet() 和 NavigationLink 都可以呈现新视图,但它们的呈现方式存在差异,应该仔细选择:
sheet() 用于显示不相关的内容,例如设置或撰写窗口NavigationLink 用于显示有关用户选择的详细信息,就像您正在深入研究某个主题一样// 写法1:在 NavigationLink 中同时提供【标签】和【目标视图】
NavigationStack {
// 标签
NavigationLink("Tap Me") {
// 目标视图
Text("Detail View")
}
}
// 写法2:如果想要用复杂点的视图作为标签,可以在 NavigationLink 中使用两个尾随闭包。例如:
NavigationStack {
NavigationLink {
// 目标视图
Text("Detail View")
} label: {
// 标签
VStack {
Text("This is the label")
Image(systemName: "face.smiling")
}
.font(.largeTitle)
}
}
<aside>
💡 SwiftUI 会自动将 NavigationLink 设置为按钮,以便用户知道它们是交互式的。您可以通过将 .buttonStyle(.plain) 应用于 NavigationLink 来禁用此行为。
</aside>
在以上写法中,只要写下了 NavigationLink,即使还没有点击按钮,还没有跳转,这个视图其实已经创建了。某些情况下这对效率是个影响:
// 创建子视图:初始化时打印一条消息
struct DetailView: View {
var number: Int
var body: some View {
Text("Detail View \\(number)")
}
init(number: Int) {
self.number = number
print("Creating detail view \\(number)")
}
}
// 创建主视图
NavigationStack {
List(0..<1000) { i in
NavigationLink("Tap Me") {
DetailView(number: i)
}
}
}
<aside> 💡
以上代码在滚动时,会看到许多 DetailView 实例正在创建,而且通常不止一次。这使得 Swift 和 SwiftUI 做了比必要的更多的工作。因此当处理动态数据时,SwiftUI 有更好的解决方案:即下面介绍的【链接标签 与 目标视图分离】。
</aside>
<aside> 💡
注意:当在 List 中使用 NavigationLink 时,会在右侧边缘看到灰色的指示器。如果注释掉 NavigationLink ,例如换成 ForEach ,将看到指示器消失。所以以下技巧只需要对 List 下的 NavigationLink 适用;对 ScrollView 下的 NavigationLink 就没必要。
</aside>
SwiftUI 没有提供关闭或隐藏指示箭头的选项。要处理这个问题,通常用两层的 ZStack 来实现导航链接。较低一层是真实内容,上层则是空视图。 NavigationLink 现在设定给空视图,避免 iOS 渲染指示箭头。修改完后,最后在 NavigationLink 视图上加上 opacity 修饰符,设置成0。
List(articles) { article in
ZStack {
MissionListCellView(mission: mission)
NavigationLink {
MissionView(mission: mission, astronauts: astronauts)
} label: {
EmptyView()
}
.opacity(0)
}
}
<aside> 💡
记得:当用 navigationDestination 进行导航,在二级视图中,不能够再用 NavigationStack ,否则会崩溃。
</aside>
在简单的导航中,我们在 NavigationLink 中同时提供【标签视图】和【目标视图】,这种做法一旦定义了 NavigationLink ,就会生成后面的二级视图。所以当遇到循环列表多的情况,就会一下创建很多二级视图,造成资源浪费。
如果您不需要高度自定义的导航,并且仅支持 iOS 16 或更高版本。强烈建议使用 navigationDestination 这种链接与目标视图分离的方式,因为它允许 SwiftUI 延迟实例化您的目标视图,减少性能损耗。使用 navigationDestination 替代 NavigationLink,可以避免在视图体内直接创建导航目标,减少重复渲染和状态更新冲突。
当使用简单的布尔值条件触发导航时,请使用 navigationDestination(isPresented:destination:) 修饰符,它将目标视图与可用于将视图推送到 NavigationStack 绑定关联。一个常见的用例是点击一个按钮来切换视图的出现,而无需传递自定义数据。
struct ContentView: View {
// 1. @State 布尔变量
@State private var isAddingProduct: Bool = false
var body: some View {
NavigationStack(path: $path) {
List {
// Views for the list here
}
// 3. 将此变量绑定到修饰符的“isPresented”参数中
.navigationDestination(isPresented: $isAddingProduct) {
// 4. 修饰符闭包中的目标视图
AddProductView()
}
.toolbar {
ToolbarItem {
Button(action: {
// 2. 将变量设置为 true 的按钮
isAddingProduct = true
}, label: {
Label("Add", systemImage: "plus")
})
}
}
}
}
}
当触发导航依赖于选择值时, navigationDestination(item:destination:) 修饰符是理想的选择。每当可选绑定值变为非 nil 时,SwiftUI 就会推送目标视图显示。
struct ContentView: View {
// 1. @State 可选值,可以是任意类型。注意,该类型需要符合 Hashable 协议
@State private var selectedFood: OtherFood? = nil
@State private var otherFoods: [OtherFood] = [
OtherFood(name: "Pizza", symbol: "🍕")
]
var body: some View {
NavigationStack(path: $path) {
List {
Section("Fruit") {
...
}
Section("Vegetables") {
...
}
Section("Other Foods") {
ForEach(otherFoods, id: \\.self) { food in
Button(action: {
// 2. 用于选择值的按钮
selectedFood = food
}, label: {
HStack {
Text(food.symbol)
Text(food.name)
}
})
}
}
}
// 3. 将可选值绑定到修饰符的 `item` 参数中
.navigationDestination(item: $selectedFood) { food in
// 4. 修饰符的闭包返回值,在这里您可以定义目标视图
OtherFoodDetailView(food: food)
}
}
}
}
NavigationLink 附加一个值,该值可以是任何值(字符串、整数、自定义结构实例或其他任何值)Hashable 的协议Hashable 协议,例如 Int String Date URL 数组、字典。如果创建了一个具有全部符合 Hashable 属性的自定义结构,则只需做一点更改,即可让该结构遵循 Hashable 协议。详情参见: Hashable//例如,此结构包含一个 UUID、一个字符串和一个整数:
struct Student {
var id = UUID()
var name: String
var age: Int
}
// 如果我们想让该结构符合 Hashable ,只需添加 :Hashable
struct Student: Hashable {
var id = UUID()
var name: String
var age: Int
}
// 现在 Student 就符合 Hashable,它可以与 NavigationLink 和 navigationDestination 一起使用,像整数或字符串一样
// Swift 大量使用 Hashable。例如当使用 Set 而不是数组时,放入其中的内容都必须符合 Hashable 协议。这就是集合比数组更快的原因
// 写法1: 只用文字
NavigationLink("链接文案", value: 1)
// 写法2: 跟一个View
NavigationLink(value: book) {
HStack {
EmojiRatingView(rating: book.rating)
}
}
// 例子:创建一个由 100 个数字组成的 List ,每个数字都附加到一个导航链接作为其表示值(我们告诉 SwiftUI 想要导航到一个数字):
NavigationStack {
List(0..<100) { i in
NavigationLink("Select \\(i)", value: i)
}
}
然后,我们需要在 NavigationStack 中(也就是列表的后面),加一个 navigationDestination() 修饰符,告诉它收到数据后要做什么。
//常规写法:
NavigationStack {
List(0..<100) { i in
NavigationLink("Select \\(i)", value: i)
}
.navigationDestination(for: Int.self) { item in
Text("You selected \\(selection)")
}
}
// 可以这样理解:等于区分开 NavigationLink、value、navigationDestination... 这几个东西。代表的意思是,首先告诉 SwiftUI 在点击 NavigationLink 时,导航到附加值 value,但导航到附加值会怎么样展示呢?它应该是文本、VStack 、自定义视图还是什么东西呢?这就是 navigationDestination 修饰符的用武之地,它里面的 for 属性代表“当你被要求导航到某类型数据时,应该展示什么...”
// 变体写法:可以做判断
NavigationStack{
VStack(spacing: 10) {
NavigationLink("导航按钮-去模版2", value: 1)
NavigationLink("导航按钮-去模版3", value: 2)
}
.navigationDestination(for: Int.self) { item in
if item == 1{
DetailView2(number: item)
}else{
DetailView3(number: item)
}
}
}
// 此时,当 SwiftUI 尝试导航到任何 Int 值时,就会在 selection 常量中提供该值,并且需要返回正确的视图来显示它。注意:
// - for 参数后面填的是:某种类型。当我们想指代这种类型的时候,要加上 `.self` ,例如 `Int.self`
// - item 代表的是输入的 `NavigationLink` 导航的 `value` 值,因此还可以用于做 if 判断
如果有多种不同类型的数据要导航,只需添加几个 navigationDestination 修饰符即可,每种类型一个。只要他们都符合 Hashable 协议。
struct ContentView: View {
var body: some View {
NavigationStack {
List {
NavigationLink("Show an integer", value: 42)
NavigationLink("Show a string", value: "Hello, world!")
NavigationLink("Show a Double", value: Double.pi)
}
.navigationDestination(for: Int.self) { Text("Received Int: \\($0)") }
.navigationDestination(for: String.self) { Text("Received String: \\($0)") }
.navigationDestination(for: Double.self) { Text("Received Double: \\($0)") }
.navigationTitle("Select a value")
}
}
}
也可以建立一个统一管理的类型,进行中转…
.navigationDestination(for: SettingDestination.self) { destination in
switch destination {
case .roleSetting:
RoleSettingPageView()
case .instruction:
InstructionView()
case .aboutApp:
AboutAppView(viewModel: viewModel)
}
}
不需要搭配 NavigationLink ,也可以实现根据值类型导航。
struct ContentView: View {
// 1. 声明一个 NavigationPath(可选),如果你想完全控制导航,可能就需要它
@State private var path = NavigationPath()
@State private var fruits = [
Fruit(name: "Apple", symbol: "🍎"),
Fruit(name: "Banana", symbol: "🍌"),
Fruit(name: "Cherry", symbol: "🍒")
]
@State private var vegetables: [Vegetable] = [
Vegetable(name: "Carrot", symbol: "🥕"),
Vegetable(name: "Broccoli", symbol: "🥦")
]
var body: some View {
NavigationStack(path: $path) {
List {
Section("Fruit") {
ForEach(fruits, id: \\.self) { fruit in
// 2.推送到 NavigationPath 值,可以是:NavigationLink 的 value 参数
NavigationLink(value: fruit) {
HStack {
Text(fruit.symbol)
Text(fruit.name)
}
}
}
}
Section("Vegetables") {
ForEach(vegetables, id: \\.self) { vegetable in
Button(action: {
// 2. 也可以以编程方式将值附加到 NavigationPath,不依赖 NavigationLink
path.append(vegetable)
}, label: {
HStack {
Text(vegetable.symbol)
Text(vegetable.name)
}
})
}
}
}
// 3. 确保您使用的类型符合 Hashable 协议,并使用修饰符处理每种类型
.navigationDestination(for: Fruit.self) { fruit in
FruitDetailView(fruit: fruit)
}
.navigationDestination(for: Vegetable.self) { vegetable in
VegetableDetailView(vegetable: vegetable)
}
}
}
}
在 SwiftUI 中,NavigationLink 默认会在点击时立即跳转到目标视图,但如果你希望在点击时执行一些额外的操作(如数据加载、状态更新、日志记录等),可以通过以下几种方式实现:
Button 和 NavigationLink 的组合,适合你想手动控制跳转的场景,可以完全控制点击事件的处理。onTapGesture,可以在点击内容时执行操作,并让 NavigationLink 正常跳转。simultaneousGesture,可以让点击行为和自定义操作同时发生,而不影响默认的跳转。你可以在点击按钮时手动控制跳转,同时在点击时执行操作。这可以通过使用 Button 和 NavigationLink 的 isActive 绑定来实现。
import SwiftUI
struct ContentView: View {
@State private var isNavigationActive = false
var body: some View {
VStack {
Button(action: {
// 在点击时执行一些操作
print("执行自定义指令")
// 触发跳转
isNavigationActive = true
}) {
Text("点击跳转并执行操作")
.foregroundColor(.blue)
}
// 使用 isActive 绑定控制 NavigationLink
NavigationLink(destination: DestinationView(), isActive: $isNavigationActive) {
EmptyView() // 使用空视图占位,因为跳转通过按钮控制
}
}
}
}
struct DestinationView: View {
var body: some View {
Text("目标视图")
}
}
Button(action:):我们使用 Button 来响应点击事件,在按钮的 action 中可以执行自定义操作,比如打印日志、更新状态等NavigationLink(isActive:):使用 isActive 绑定来控制 NavigationLink 是否激活。这样我们可以通过设置 isNavigationActive 为 true 来触发跳转如果想继续使用 NavigationLink 的外观而不显式使用按钮,也可以在 NavigationLink 包裹的内容上使用 onTapGesture,执行自定义操作后再进行跳转。
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: DestinationView()) {
Text("点击跳转并执行操作")
.foregroundColor(.blue)
.onTapGesture {
// 在点击时执行一些操作
print("执行自定义指令")
}
}
}
}
}
struct DestinationView: View {
var body: some View {
Text("目标视图")
}
}
onTapGesture:使用 onTapGesture 可以在 NavigationLink 的内容上添加点击手势,在跳转前执行自定义指令。虽然 NavigationLink 会在点击时自动跳转,但手势会首先触发并执行操作。