在 SwiftUI 中,NavigationStack 和 NavigationView 都可用于构建导航界面,主要区别是:
- avigationStack 是新一代导航容器,NavigationView 将被逐步淘汰。
- NavigationStack 使用路径来定义导航状态,更灵活
- 更加节省资源
NavigationView例子
SwiftUI
struct NavigationStackSample: View {
private var countries: [String] = ["China", "Japan", "France", "Korea", "United state"]
var body: some View {
NavigationView {
ScrollView {
VStack(spacing: 40, content: {
ForEach(countries, id: .self) { country in
NavigationLink {
CountryView(country: country)
} label: {
Text(country)
}
}
})
}
.navigationTitle("NavigationStack")
}
}
}
struct CountryView: View {
var country: String
init(country: String) {
self.country = country
print("【Log】: (country)")
}
var body: some View {
Text(country)
}
}
例子中使用数组循环了一个列表,点击列表中的列,可以导航到下一级页面
我们可以看到控制台输出的log,可以确定循环的列表中的页面都已经被初始化了
如果数组有100条数据,那么就会初始化CountryView 100 次。不管用不用,都先初始化了,这样其实很浪费资源,100个不一定你每一个都会去看。如果是新闻列表,如果你不继续把列表往下翻,那么屏幕以外加载的数据就白白浪费了
NavigationStack中类型绑定
我们把上述例子中使用的NavigationView改成NavigationStack看看效果。
NavigationStack需要和一些固定的搭配来使用,下面是官方的一个示例
SwiftUI
NavigationStack(path: $presentedParks) {
List(parks) { park in
NavigationLink(park.name, value: park)
}
.navigationDestination(for: Park.self) { park in
ParkDetails(park: park)
}
}
他需要和navigationDestination 方法来搭配使用。
我们也来模仿改改
SwiftUI
struct NagationStackSample: View {
private var countries: [String] = ["China", "Japan", "France", "Korea", "United state"]
var body: some View {
NavigationStack {
ScrollView {
VStack(spacing: 40, content: {
ForEach(countries, id: .self) { country in
NavigationLink(value: country) {
Text("country (country)")
}
}
})
}
.navigationTitle("NavigationStack")
.navigationDestination(for: String.self) { country in
CountryView(country: country)
}
}
}
}
改成以上代码后,效果和之前的NavigationView效果一样。但是控制台无任何输出。当你点击某一条数据时才会真正的去初始化。
点击后代码会走navigationDestination 方法,这时会真的去初始化。到目前为止一切都是正常的。我们再添加一些数据代码,用Int类型循环一些数据
SwiftUI
ForEach(0..<10) { i in
NavigationLink(value: i) {
Text("Index: (i)")
}
}
我们把以上代码添加到主视图的VStack里面。
当我们点击新加入的行,此时却没有任何反应。控制台输了一些信息
SwiftUI
A NavigationLink is presenting a value of type "Int" but there is no matching navigationDestination declaration visible from the location of the link. The link cannot be activated.
Note: Links search for destinations in any surrounding NavigationStack, then within the same column of a NavigationSplitView.
意思是我在navigationDestination 方法中给的是String 类型,但是点击的数据是Int类型。所以导致无法工作。
此时我们只需要再补充一个 navigationDestination 方法即可。只不过这里的类型要改成Int
SwiftUI
.navigationDestination(for: Int.self, destination: { i in
Text("View (i)")
})
这样就可以正常来使用。
所以在NavigationStack 中的navigationDestination方法参数是一个要注意的地方。
SwiftUI
struct NavigationStackSample: View {
private var countries: [String] = ["China", "Japan", "France", "Korea", "United state"]
var body: some View {
NavigationStack {
ScrollView {
VStack(spacing: 40, content: {
ForEach(countries, id: .self) { country in
NavigationLink(value: country) {
Text("country (country)")
}
}
ForEach(0..<10) { i in
NavigationLink(value: i) {
Text("Index: (i)")
}
}
})
}
.navigationDestination(for: String.self, destination: { country in
CountryView(country: country)
})
.navigationDestination(for: Int.self, destination: { i in
Text("View (i)")
})
.navigationTitle("NavigationStack")
}
}
}
NavigationStack 使用路径来定义导航
自定义试图,我们就需要使用NavigationStack初始化的另一个方法来实现。方法定义如下,它需要传入一个Binding 类型的数据.
SwiftUI
@MainActor public init(
path: Binding<NavigationPath>,
@ViewBuilder root: () -> Root
) where Data == NavigationPath
我们先定一个NavigationPath()
SwiftUI
@State private var path = NavigationPath()
将之前的方法替换为如下方法
SwiftUI
NavigationStack(path: $path) {}
如果我们不限定path的类型,就是用NavigationPath() 来进行初始化。如果想限定指定的类型可以使用如下代码:
SwiftUI
@State private var path: [String] = []
上面类型限定为String,所以在列表 中,只有数据类型是String的才可以点击触发正常,其他类型就会报错。原理和上面的问题是一样的。
那么NavigationPath是什么呢?
NavigationPath 用于定义 NavigationStack 的导航状态。其主要功能包括:
- 描述导航堆栈,NavigationPath维护一个视图的路径堆栈,表示当前的导航状态。
- 指定导航顺序,通过在Path内列出视图,可以定义精确的导航顺序。
- 标识视图,通过给视图加上标识符,可以唯一定位一个视图在路径中的位置。
- 当前视图,NavigationPath可以获取当前展示的视图。
- 响应导航事件,路径变化时会触发回调,可以处理导航逻辑。
- 绑定处理,通过@Binding可以将NavigationPath绑定到视图中。
说了这么多,好像我也不明白。那么我们来看看例子
我们往ScrollerView里面添加以下代码:
SwiftUI
Button {
path.append("UK")
path.append("India")
path.append("Philippines")
path.append(1000)
} label: {
Text("PUSH")
.padding()
}
效果如下:
它会把所有放在path里面的数据进行push 操作。由于我的path数据类型是NavigationPath , 所以我可以往里面添加String 和Int类型的值
这样对于想跳转到指定页面的需求就很好解决了,我们只需维护一个NavigationPath就可以完成这个需求。
全部代码如下:
SwiftUI
struct NavigationStackSample: View {
private var countries: [String] = ["China", "Japan", "France", "Korea", "United state"]
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
ScrollView {
Button {
path.append("UK")
path.append("India")
path.append("Philippines")
path.append(1000)
} label: {
Text("PUSH")
.padding()
}
VStack(spacing: 40, content: {
ForEach(countries, id: .self) { country in
NavigationLink(value: country) {
Text("country (country)")
}
}
ForEach(0..<10) { i in
NavigationLink(value: i) {
Text("Index: (i)")
}
}
})
}
.navigationDestination(for: String.self, destination: { country in
CountryView(country: country)
})
.navigationDestination(for: Int.self, destination: { i in
Text("View (i)")
})
.navigationTitle("NavigationStack")
}
}
}
struct CountryView: View {
var country: String
init(country: String) {
self.country = country
print("【Log】: (country)")
}
var body: some View {
Text(country)
}
}
大家有什么看法呢?欢迎留言讨论。
公众号:RobotPBQ