--- title: 'How do I pop to a specific view in SwiftUI?' tags: SwiftUI disqus: hackmd --- **目錄:** [TOC] ## 前言 由於 SwiftUI 目前只有在 NavigationStack 提供內建 to root 功能 因此 iOS 15 以下我們需要自己寫一套去控制 ## 範例一 創建 State ObservableObject ```swift= class NavigationState: ObservableObject { @Published var stack: [Int] = [] func push(_ page: Int) { stack.append(page) } func popTo(_ page: Int) { while stack.last != page && !stack.isEmpty { stack.removeLast() } } } ``` 首頁監聽 State 變化,並將整個 StateObject 傳遞給跳轉的第一頁 ```swift= struct ContentView: View { @StateObject private var navigationState = NavigationState() var body: some View { NavigationView { VStack { Text("Home Page") NavigationLink( destination: FirstView(navigationState: navigationState), isActive: Binding( get: { navigationState.stack.contains(1) }, set: { if $0 { navigationState.push(1) } else { navigationState.popTo(0) } } ) ) { Text("Go to First Page") } .isDetailLink(false) } } } } ``` 跳轉的第一頁,我們也會去監聽 State,並且設定跳頁功能,然後將 ObservedObject 繼續傳遞給第二頁 ```swift= struct FirstView: View { @ObservedObject var navigationState: NavigationState var body: some View { VStack { Text("First Page") NavigationLink( destination: SecondView(navigationState: navigationState), isActive: Binding( get: { navigationState.stack.contains(2) }, set: { if $0 { navigationState.push(2) } else { navigationState.popTo(1) } } ) ) { Text("Go to Second Page") } .isDetailLink(false) } } } ``` 跳頁的第二頁,設定要返回的設定 ```swift= struct SecondView: View { @ObservedObject var navigationState: NavigationState var body: some View { VStack { Text("Second Page") Button("Go Back to Root Page") { navigationState.popTo(0) } } } } ``` ## 範例二 創建環境變數 ```swift= import SwiftUI struct RootPresentationModeKey: EnvironmentKey { static let defaultValue: Binding<RootPresentationMode> = .constant(RootPresentationMode()) } extension EnvironmentValues { var rootPresentationMode: Binding<RootPresentationMode> { get { return self[RootPresentationModeKey.self] } set { self[RootPresentationModeKey.self] = newValue } } } typealias RootPresentationMode = Bool extension RootPresentationMode { public mutating func dismiss() { self.toggle() } } ``` 第一頁綁定 isActive 去監聽,並設定給環境變數 ```swift= struct ContentView: View { @State private var isActive : Bool = false var body: some View { NavigationView { VStack { Text("Root") NavigationLink(destination: ContentView2(), isActive: self.$isActive) { Text("Push") } .isDetailLink(false) } .navigationBarTitle("Root") } .navigationViewStyle(StackNavigationViewStyle()) .environment(\.rootPresentationMode, self.$isActive) } } ``` 第二頁取到環境變數後,可以利用環境變數去設定要跳頁或是要返回 這邊跟第一頁一樣有綁定 isActive 是因為想要由後面頁面返回到這頁,但這邊用範例一的方法,直接傳遞值下去設定,也可以改成環境變數。 ```swift= struct ContentView2: View { @State private var isActive: Bool = false @Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode> @Environment(\.rootPresentationMode) private var rootPresentationMode: Binding<RootPresentationMode> var body: some View { VStack { Text("Two") NavigationLink(destination: ContentView3(), isActive: self.$isActive) { Text("Push") } .isDetailLink(false) Button (action: { self.presentationMode.wrappedValue.dismiss() } ) { Text("Pop") } Button (action: { self.rootPresentationMode.wrappedValue.dismiss() } ) { Text("Pop to root") } } .navigationBarTitle("Two") } } ``` 第三頁一樣會去取環境變數進行跳頁的功能 ```swift= struct ContentView3: View { @Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode> @Environment(\.rootPresentationMode) private var rootPresentationMode: Binding<RootPresentationMode> var body: some View { VStack { Text("Three") Button (action: { self.presentationMode.wrappedValue.dismiss() } ) { Text("Pop") } Button (action: { self.rootPresentationMode.wrappedValue.dismiss() } ) { Text("Pop to root") } } .navigationBarTitle("Three") } } ``` ## NavigationPath 這方法只適用於 NavigationStack,因此只支援 iOS 16 以上 ```swift= enum Router: String { case view2, view3, view4 } struct ContentView: View { @State private var path = NavigationPath() var body: some View { NavigationStack(path: $path) { VStack { Text("View 1") NavigationLink(Router.view2.rawValue, value: Router.view2) } .navigationTitle("View 1") .navigationDestination(for: Router.self) { router in switch router { case .view2: VStack { Text("View 2") NavigationLink("Show view 3", value: Router.view3) } .navigationTitle("View 2") case .view3: VStack { Text("View 3") NavigationLink("Show view 4", value: Router.view4) } .navigationTitle("View 3") case .view4: VStack { Text("View 4") Button("Pop to view 2") { // Remove from path to pop back path.removeLast(2) } } .navigationTitle("View 4") } } } } } ``` 可以發現實作起來非常簡單,只要控制一個 Path 也不需要傳遞參數,就能達成效果了。