最近做 SwiftUI 项目,之前对于 navigationDestination 的用法理解不太深刻,觉得很是难用,最近发现了正确的使用方式,这里记录一下。
假设有一个 TabView 类为 A,A 有 B、C 两个Tab,C 的 Tab 下子界面有 D,D 的子界面有 E。
即有 A -> B 和 A -> C -> D -> E 两条链路。
之前的用法是:
struct A: View {
var body: some View {
NavigationStack {
TabView(selection: $selectedTab) {
B()
C()
}
}
}
}
struct B: View {
}
struct C: View {
@State private var navigateToD: Bool = false
var body: some View {
VStack {
xxx
Button {
navigateToD.toggle()
} label: {
Text("NavigateToD")
}
}
.navigationDestination(isPresented: $navigateToD) {
D()
}
}
}
struct D: View {
@State private var navigateToE: Bool = false
var body: some View {
VStack {
xxx
Button {
navigateToE.toggle()
} label: {
Text("NavigateToD")
}
}
.navigationDestination(isPresented: $navigateToE) {
E()
}
}
}
struct E: View {
xxx
}
这里面简单的使用确实没问题,每个界面的返回可以通过 Environment 的dismiss 来实现。但是如果想要实现从 E 返回到 C 就非常麻烦了。而且,这里每一步的跳转都散落在各个类里,没有统一的地方管理,后续维护也不易。
所以针对上面存在的问题,对使用进行了优化,
针对TabView 的两个子视图,B 和 C,分别用NavigationStack
包装。不要把NavigationStack
放在TabView
的外层,因为遇到了放在这里,针对navigationDestination做跳转的时候,遇到了跳转多次的问题。
声明一个BNavCoordinator
和CNavCoordinator
,分别用于管理B
和C
的跳转。在具体的NavCoordinator
中,声明一个枚举管理这个页面下的所有子界面。然后创建NavCoordinator
,实现push
、pop
、popToRoot
和navigator
方法。
示例代码如下:
enum CNavScreens: Hashable {
case d(param1: Int, param2: String)
case e
}
struct CNavCoordinator: ObservableObject {
@Published var paths = NavigationPath()
@ViewBuilder
func navigate(to screen: CNavScreens) -> some View {
switch screen {
case .d(let param1, let param2):
D(param1: param1, param2: param2)
case .e:
E()
}
}
// add screen
func push(_ screen: TGICustomerScreens) {
paths.append(screen)
}
// remove last screen
func pop() {
paths.removeLast()
}
// popToRoot
func popToRoot() {
paths.removeLast(paths.count)
}
}
然后在具体页面中使用,示例如下
struct A: View {
var body: some View {
TabView(selection: $selectedTab) {
B()
C()
}
}
}
struct B: View {
}
struct C: View {
@StateObject var cNavCoordinator = CNavCoordinator()
var body: some View {
NavigationStack {
VStack {
xxx
Button {
let screen = CNavScreens.d(param1: param1, param2: param2)
cNavCoordinator.push(screen)
} label: {
Text("NavigateToD")
}
}
.environmentObject(navCoordinator)
.navigationDestination(for: CustomScreens.self) { path in
navCoordinator.navigate(to: path)
.environmentObject(navCoordinator)
.environmentObject(xxx)
.environmentObject(yyy)
}
}
}
}
struct D: View {
@EnvironmentObject var cNavCoordinator: CNavCoordinator
var body: some View {
VStack {
xxx
Button {
let screen = CNavScreens.e
cNavCoordinator.push(screen)
} label: {
Text("NavigateToD")
}
}
}
}
struct E: View {
@EnvironmentObject var cNavCoordinator: CNavCoordinator
var body: some View {
VStack {
xxx
Button {
// cNavCoordinator.pop()
cNavCoordinator.popToRoot()
} label: {
Text("BackToC")
}
}
}
}
这样所有的跳转其实都是在根类 B 和 C 中管理,避免了分散到每个页面的逻辑。同时可以方便返回到根视图。