在 SwiftUI 中,NavigationStack 和 NavigationView 都可用于构建导航界面,首要区别是:
- avigationStack 是新一代导航容器,NavigationView 将被逐渐筛选。
- NavigationStack 运用途径来界说导航状况,更灵活
- 更加节省资源
NavigationView比如
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需要和一些固定的调配来运用,下面是官方的一个示例
NavigationStack(path: $presentedParks) {
List(parks) { park in
NavigationLink(park.name, value: park)
}
.navigationDestination(for: Park.self) { park in
ParkDetails(park: park)
}
}
他需要和navigationDestination 办法来调配运用。
咱们也来模仿改改
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类型循环一些数据
ForEach(0..<10) { i in
NavigationLink(value: i) {
Text("Index: (i)")
}
}
咱们把以上代码增加到主视图的VStack里面。
当咱们点击新参加的行,此时却没有任何反应。控制台输了一些信息
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
.navigationDestination(for: Int.self, destination: { i in
Text("View (i)")
})
这样就能够正常来运用。
所以在NavigationStack中的navigationDestination办法参数是一个要注意的当地。
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 类型的数据.
@MainActor public init(
path: Binding<NavigationPath>,
@ViewBuilder root: () -> Root
) where Data == NavigationPath
咱们先定一个NavigationPath()
@State private var path = NavigationPath()
将之前的办法替换为如下办法
NavigationStack(path: $path) {}
如果咱们不限制path的类型,就是用NavigationPath() 来进行初始化。如果想限制指定的类型能够运用如下代码:
@State private var path: [String] = []
上面类型限制为String,所以在列表 中,只要数据类型是String的才能够点击触发正常,其他类型就会报错。原理和上面的问题是相同的。
那么NavigationPath是什么呢?
NavigationPath 用于界说 NavigationStack 的导航状况。其首要功能包括:
- 描绘导航仓库,NavigationPath保护一个视图的途径仓库,表明当前的导航状况。
- 指定导航顺序,经过在Path内列出视图,能够界说精确的导航顺序。
- 标识视图,经过给视图加上标识符,能够仅有定位一个视图在途径中的位置。
- 当前视图,NavigationPath能够获取当前展现的视图。
- 呼应导航事件,途径变化时会触发回调,能够处理导航逻辑。
- 绑定处理,经过@Binding能够将NavigationPath绑定到视图中。
说了这么多,如同我也不明白。那么咱们来看看比如
咱们往ScrollerView里面增加以下代码:
Button {
path.append("UK")
path.append("India")
path.append("Philippines")
path.append(1000)
} label: {
Text("PUSH")
.padding()
}
作用如下:
它会把所有放在path里面的数据进行push操作。因为我的path数据类型是NavigationPath, 所以我能够往里面增加String和Int类型的值
这样对于想跳转到指定页面的需求就很好解决了,咱们只需保护一个NavigationPath就能够完成这个需求。
悉数代码如下:
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