---
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 也不需要傳遞參數,就能達成效果了。