見出し画像

NavigationStackで画面遷移をコードで制御

SwiftUIでiOS 16から従来のNavigationViewによる画面遷移がdeprecatedになり、代わりにNavigationStackが発表された。NavigationStackを使うことで、従来では難しかったコードベースでの画面遷移や遷移元の特定画面に戻るなどの制御がしやすくなる。

NavigationView

まず従来のNavigationViewでの画面遷移方法だが、基本的な画面遷移は以下のようにNaviagtionViewをルートViewとして、NavigationLinkで遷移先のViewとLabelを指定する。

struct ContentView: View {
    // 基本的な画面遷移
    var body: some View {
        NavigationView {
            NavigationLink(destination: SecondView()) {
                Text("Next")
            }
        }
    }
}

struct SecondView: View {
    var body: some View {
        Text("Second View")
    }
}

また、コードベースで画面遷移を制御する場合は、Binding<Bool>型の変数を宣言して、NavigationLinkのisActive(公式ドキュメントを参照)に渡せば、ボタン押下などでその変数をtrueにすることで画面遷移を行うことができる。

struct ContentView: View {
    // フラグで画面遷移を制御
    @State private var isActive = false
    
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: SecondView(), isActive: $isActive) {
                    EmptyView()
                }

                Button {
                    isActive = true
                } label: {
                    Text("Next")
                }
            }
        }
    }
}

補足として、上記のNavigationLink(...) { EmptyView() }をもっと簡潔に書ける方法があり、こちらの記事で紹介されている。

課題点

NavigationViewの課題点としては、コードで画面遷移を制御する際に、リンクごとにフラグが必要で管理が煩雑になってしまう。また、遷移先から特定の画面に戻ろうとする場合は、バケツリレーのようにフラグを子Viewに渡して、任意のタイミングでフラグを更新する必要がある。

NavigationStack

新しく登場したNavigationStackでは、まずNavigationLinkやフラグ制御のような画面遷移は今までと同様にできる。

// 基本的な画面遷移
struct ContentView: View {
    var body: some View {
        NavigationStack {
            NavigationLink(destination: SecondView()) {
                Text("Next")
            }
        }
    }
}

// フラグで画面遷移を制御
struct ContentView: View {
    @State private var isPresented = false

    var body: some View {
        NavigationStack {
            Button {
                isPresented = true
            } label: {
                Text("Next")
            }
            .navigationDestination(isPresented: $isPresented) {
                SecondView()
            }
        }
    }
}

そして、NavigationStackのメリットとして、コードベースでの画面遷移や遷移元の特定画面に戻るなどの制御がしやすく、具体的には以下のようにNavigationStackにpathの配列を渡して、その配列を変更することで画面遷移を制御できる。

enum Path: String, Hashable {
    case screen1, screen2
}

struct ContentView: View {
    @State private var path = [Path]()
    
    var body: some View {
        NavigationStack(path: $path) {
            VStack(spacing: 16) {
                Button("Show Screen1") {
                    // pathにscreen1を追加することでScreen1に遷移する
                    path.append(.screen1)
                }
                
                Button("Show Screen2") {
                    // pathにscreen1,screen2を追加して、一気にscreen2に遷移する
                    path = [.screen1, .screen2]
                }
            }
            .navigationTitle("Top")
            .navigationBarTitleDisplayMode(.inline)
            .navigationDestination(for: Path.self) {
                // 遷移先にpath配列の参照や必要な情報を渡す
                ScreenView(path: $path, title: $0.rawValue)
            }
        }
    }
}

struct ScreenView: View {
    // 親Viewからpath配列の参照を受け取る
    @Binding var path: [Path]

    var title: String = ""
    
    var body: some View {
        VStack(spacing: 16) {
            Button("Back") {
                // path配列を操作して前画面に戻る
                path.removeLast()
            }
            
            Button("Back to top") {
                // path配列の全要素を削除すれば最初の画面に戻れる
                // UINavigationController.popToRootViewControllerのような動作ができる
                path.removeAll()
            }
        }
        .navigationTitle(title)
    }
}

デモ

上記コードの動作デモ。Top画面でpath配列に要素を追加・設定することで画面遷移でき、配列から要素を削除・クリアすることで前画面やTop画面に戻れることがわかる。

NavigationStackのデモ

このようにNavigationStackを使えば、コードベースでの画面遷移制御が行いやすくなるため、ディープリンクやCoordinatorパターンの実装がより簡単になる。SwiftUIは手軽にUIを組めるメリットは非常に大きいが、UIKitに頼らざるを得ない細かい部分も改善されているので、さらに今後に期待。

参考


この記事が気に入ったらサポートをしてみませんか?