SwiftUI中的NavigationStack是iOS 16引进根据数据类型驱动的导航视图。经过将数据类型与视图相关起来,供给了类型安全的运用办法。本文并非介绍NavigationStack根本运用的文章,故这儿就不多做介绍了。
在实际运用中,经过编码的办法保护导航视图栈对错常常见的行为,比方:翻开不同的视图;退出部分视图等。虽然在官方的NavigationStack示例中,也供给了保护导航视图途径的办法,但仅能支撑一些简单的场景。别的,在运用上也存在一些局限性。整体的可操作性与UIKit中的UINavigationController有些差距。
下面先来看看示例文档中途径绑定的比如。
NavigationStack的途径绑定办法
当需求保护多个视图界面时,经过在初始化办法中传入绑定path的数组,然后经过对数组的操作来完结视图的导航,比方:给数组插入元素或许删去元素,即完结界面的翻开和退出。
@State private var presentedParks: [Park] = []
NavigationStack(path: $presentedParks) {
List(parks) { park in
NavigationLink(park.name, value: park)
}
.navigationDestination(for: Park.self) { park in
ParkDetails(park: park)
}
}
这个示例中的问题是只能支撑翻开相同的界面,由于绑定的数组容器元素是一个详细的类型。
为了处理这个问题,需求运用到类型无关的列表容器NavigationPath,初始化path参数也支撑传入绑定NavigationPath。
@State private var presentedPaths = NavigationPath()
NavigationStack(path: $presentedPaths) {
List {
ForEach(parks) { park in
NavigationLink(park.name, value: park)
}
ForEach(zoos) { zoo in
NavigationLink(zoo.name, value: zoo)
}
}
.navigationDestination(for: Park.self) { park in
ParkDetails(park: park)
}
.navigationDestination(for: Zoo.self) { zoo in
ZooDetails(park: zoo)
}
}
NavigationPath也支撑简单的数据增加和删去元素的等操作。
/// Appends a new value to the end of this path.
public mutating func append<V>(_ value: V) where V : Hashable
/// Appends a new codable value to the end of this path.
public mutating func append<V>(_ value: V) where V : Decodable, V : Encodable, V : Hashable
/// Removes values from the end of this path.
///
/// - Parameters:
/// - k: The number of values to remove. The default value is `1`.
///
/// - Precondition: The input parameter `k` must be greater than or equal
/// to zero, and must be less than or equal to the number of elements in
/// the path.
public mutating func removeLast(_ k: Int = 1)
日常运用中的问题
虽然上述2种办法供给了保护视图栈的办法,但在实际运用过程中还是会有一些小问题:
- 总是需求创建一个类型来相关视图。比方:某些静态界面或许本来就不需求传入参数的视图。
- 类型与单个视图绑定。比方:多个视图接收相同的模型参数。
- 在视图中无法直接获取绑定的导航数据列表容器。
- NavigationPath中供给的容器操纵办法不行。
根据枚举的途径绑定
Swift中的枚举十分强大,跟Objective-C和C中的完全不是一种东西。乃至可以说如果不会运用枚举,就根本不了解Swift。这篇文章十分主张阅读:Swift 最佳实践之 Enum。
首先,咱们经过枚举来表明运用中的视图类型,结合嵌套的枚举来表明子级的视图类型。别的,经过相关值来传递子视图或视图的入参。
enum AppViewRouter: Hashable {
enum Category: Hashable {
case list
case detail(Int)
}
case profile
case category(Category)
var title: String {
switch self {
case .profile:
return "Profile"
case .category(let category):
switch category {
case .list:
return "Category List"
case .detail(let id):
return "Category Detail: (id)"
}
}
}
}
在NavigationStack的初始化办法中,经过包括视图枚举的数组来进行绑定。在一个navigationDestination中经过不同的枚举来完结对不同的视图创建,经过编译器,也可以协助咱们不会遗失没有处理的视图。
struct ContentView: View {
@State
private var presentedRouters: [AppViewRouter] = []
var body: some View {
NavigationStack(path: $presentedRouters) {
LinkView(title: "Home")
.navigationBarTitleDisplayMode(.inline)
.navigationDestination(for: AppViewRouter.self) { park in
switch park {
case .profile:
LinkView(title: park.title)
case .category(let category):
switch category {
case .list:
LinkView(title: park.title)
case .detail(let id):
LinkView(title: park.title)
}
}
}
}
.environment(.myNavigationPath, $presentedRouters)
}
}
为了能够在子视图中操作当时的导航栈,咱们创建了一个环境Binding值,将当时的绑定的枚举数组注入到环境中。
private struct MyNavigationPathKey: EnvironmentKey {
static let defaultValue: Binding<[AppViewRouter]> = .constant([AppViewRouter]())
}
extension EnvironmentValues {
var myNavigationPath: Binding<[AppViewRouter]> {
get { self[MyNavigationPathKey.self] }
set { self[MyNavigationPathKey.self] = newValue }
}
}
在LinkView中,咱们获取绑定途径的环境Binding值,经过对途径数据的增加或删去操作,以实现导航栈的操控。当然,也可以运用NavigationLink持续翻开新的视图,以一种类型安全并且带有层级结构的办法。
struct LinkView: View {
let title: String
@Environment(.myNavigationPath) var customValue
var body: some View {
VStack(alignment: .leading, spacing: 20) {
NavigationLink("Go Profile", value: AppViewRouter.profile)
NavigationLink("Go Category List", value: AppViewRouter.category(.list))
Button("Go Category Detail") {
customValue.wrappedValue.append(AppViewRouter.category(.detail(999)))
}
Button("Back") {
if !customValue.wrappedValue.isEmpty {
customValue.wrappedValue.removeLast()
}
}
Button("Back to Root") {
customValue.wrappedValue.removeAll()
}
}
.padding()
.navigationTitle(title)
}
}
总结
经过上述的比如咱们可以看到,运用枚举来界说运用的视图层级是一种十分的好的办法,而相关值也很好的处理了视图的入参问题。将绑定的视图数组注入到环境中,也能让子视图便利的操控整个界面的导航。
整个过程不需求涉及新的内容,都是一些在SwiftUI开发中的常见的部分。但在运用体会上要比之前好得多。
完整Demo地址:Github地址