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 延迟实例化您的目标视图,减少性能损耗。具体做法如下:
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")
}
}
}
在 SwiftUI 中,NavigationLink
默认会在点击时立即跳转到目标视图,但如果你希望在点击时执行一些额外的操作(如数据加载、状态更新、日志记录等),可以通过以下几种方式实现:
Button
和 NavigationLink
的组合,适合你想手动控制跳转的场景,可以完全控制点击事件的处理。onTapGesture
,可以在点击内容时执行操作,并让 NavigationLink
正常跳转。simultaneousGesture
,可以让点击行为和自定义操作同时发生,而不影响默认的跳转。Button
结合 NavigationLink
你可以在点击按钮时手动控制跳转,同时在点击时执行操作。这可以通过使用 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
来触发跳转onTapGesture
与 NavigationLink
如果想继续使用 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
会在点击时自动跳转,但手势会首先触发并执行操作。
simultaneousGesture
如果你想保留 NavigationLink
的默认点击行为,同时执行额外操作,可以使用 simultaneousGesture
,这样不会干扰默认的跳转行为。
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: DestinationView()) {
Text("点击跳转并执行操作")
.foregroundColor(.blue)
}
.simultaneousGesture(TapGesture().onEnded {
// 在点击时执行一些操作
print("执行自定义指令")
})
}
}
}
struct DestinationView: View {
var body: some View {
Text("目标视图")
}
}
simultaneousGesture
:它允许你在不阻止 NavigationLink
的默认行为的情况下,绑定一个手势来执行自定义操作。在点击 NavigationLink
时,手势会被触发,但跳转行为也会正常发生。