背景
最近做 SwiftUI 项目,之前对于 navigationDestination 的用法理解不太深刻,觉得很是难用,最近发现了正确的使用方式,这里记录一下。
场景
假设有一个 TabView 类为 A,A 有 B、C 两个Tab,C 的 Tab 下子界面有 D,D 的子界面有 E。
即有 A -> B 和 A -> C -> D -> E 两条链路。
之前的用法是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| 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方法。
示例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 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() } }
func push(_ screen: TGICustomerScreens) { paths.append(screen) } func pop() { paths.removeLast() } func popToRoot() { paths.removeLast(paths.count) } }
|
- 然后在具体页面中使用,示例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| 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.popToRoot() } label: { Text("BackToC") } } } }
|
这样所有的跳转其实都是在根类 B 和 C 中管理,避免了分散到每个页面的逻辑。同时可以方便返回到根视图。