NavigationStack in SwiftUI

在 SwiftUI 中,NavigationStack 和 NavigationView 都可用于构建导航界面,主要区别是:

  1. avigationStack 是新一代导航容器,NavigationView 将被逐步淘汰。
  2. NavigationStack 使用路径来定义导航状态,更灵活
  3. 更加节省资源
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个不一定你每一个都会去看。如果是新闻列表,如果你不继续把列表往下翻,那么屏幕以外加载的数据就白白浪费了

我们把上述例子中使用的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初始化的另一个方法来实现。方法定义如下,它需要传入一个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 的导航状态。其主要功能包括:

  1. 描述导航堆栈,NavigationPath维护一个视图的路径堆栈,表示当前的导航状态。
  2. 指定导航顺序,通过在Path内列出视图,可以定义精确的导航顺序。
  3. 标识视图,通过给视图加上标识符,可以唯一定位一个视图在路径中的位置。
  4. 当前视图,NavigationPath可以获取当前展示的视图。
  5. 响应导航事件,路径变化时会触发回调,可以处理导航逻辑。
  6. 绑定处理,通过@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 , 所以我可以往里面添加StringInt类型的值

这样对于想跳转到指定页面的需求就很好解决了,我们只需维护一个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

相关推荐
东坡肘子2 天前
肘子的 Swift 周报 #063|异种肾脏移植取得突破
swiftui·swift·apple
恋猫de小郭3 天前
什么?Flutter 可能会被 SwiftUI/ArkUI 化?全新的 Flutter Roadmap
flutter·ios·swiftui
靴子学长4 天前
iOS + watchOS Tourism App(含源码可简单复现)
mysql·ios·swiftui
hxx22110 天前
iOS swift开发系列--如何给swiftui内容视图添加背景图片显示
ios·swiftui·swift
胖虎110 天前
SwiftUI - (十九)组合视图
ios·swiftui·swift·组合视图
davidson147111 天前
Xcode
ios·swiftui·xcode·swift·apple
大熊猫侯佩12 天前
苹果开发者入门:修复 SwiftUI 中“跑偏的”动画(下)
swiftui·动画·animation·transition·转场·显式隐式动画·布局坐标
_rufeng_17 天前
SwiftUI入门篇
ios·swiftui·swift
大熊猫侯佩19 天前
SwiftUI 列表(或 Form)子项中的 Picker 引起导航无法跳转的原因及解决
list·swiftui·form·列表·navigation·导航·picker
袁代码1 个月前
SwiftUI开发教程系列 - 第十二章:本地化与多语言支持
开发语言·前端·ios·swiftui·swift·ios开发