【SwiftUI】EnvironmentObjectを使って先頭のViewに戻る方法【画面遷移】

今回は、NavigationView と NavigationLink によって複数階層に潜った状態で最初の View に戻る方法(UIKit でというところの popToRootViewController)について説明しますが、実は以前に下記の記事で既に紹介しています。

この記事では、最初の View の NavigationLinkisActive: Binding<Bool> に紐づけている変数を次の画面に順に受け渡していく方法を紹介しました。

しかし、この方法ではナビゲーション 上の全ての View に受け渡し用の変数を定義し、また、中間の View の NavigationLink には isDetailLink(false) を設定しておかなければなりません

そのため、5画面や10画面など、より深い層までナビゲーションしていくアプリの場合、全ての画面に対応する必要があり非常に面倒です。

今回は、この問題を解決するよりシンプルな方法について紹介したいと思います。

EnvironmentObjectを使って先頭のViewに戻る方法

EnvironmentObject とは

今回のポイントは EnvironmentObject です。

EnvironmentObject は環境変数という意味で、イメージとしてはシングルトンオブジェクトに比較的近いものかと思います。シングルトンはアプリケーション全体で共有することが前提であることに対して、EnvironmentObject は遷移関係のある View 間だけで共有させることできます

この EnvironmentObject に、先頭のViewから次のViewへの遷移フラグであるBool変数(Binding<Bool>)を保持しておき、先頭に戻りたい遷移先のViewでこの変数を取り出してフラグを下げる(falseにする)ことで一気に先頭のViewに戻ることができます

また、複数階層の中間階層のViewの NavigationLinkisDetailLink(false) を設定しておく必要もありません

説明はこのくらいにして、具体的なコード例をみていきましょう。

EnvironmentObject を使って先頭に戻る例

この例では、先頭(FirstView)を含めて4画面の階層で構成しています。

EnvironmentData

class EnvironmentData: ObservableObject {
    @Published var isNavigationActive: Binding<Bool> = Binding<Bool>.constant(false)
}

まず、先頭の View に EnvironmentObject として登録するデータを定義します。

ObservableObject プロトコルに準拠する必要があります。

そして、今回はナビゲーションのトリガーとなる isNavigationActive: Binding<Bool> を宣言していますが、用途によって IntString 等のデータを宣言することもできます。

FirstView(先頭View)

struct FirstView: View {
    
    @State var isActive: Bool = false
    @EnvironmentObject var envData: EnvironmentData
    
    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: SecondView(), isActive: $isActive) {
                    EmptyView()
                }
                Button("Forward to Second View.") {
                    isActive = true
                    envData.isNavigationActive = $isActive
                }
            }
        }.navigationBarTitle("First View")
    }
}

@State var isActive: BoolNavigationLink のトリガーです。

@EnvironmentObject var envData: EnvironmentData が環境変数です。

ボタンを押下したときに、envData に定義している isNavigationActiveisActive を紐づけています。

FirstView に EnvironmentData をセットする

FirstView の生成時に .environmentObject(bindable: ObservableObject) で、EnvironmentData をセットします。

@main
struct Test056_NavigationApp: App {
    var body: some Scene {
        WindowGroup {
            FirstView().environmentObject(EnvironmentData())
        }
    }
}

これをやり忘れるとクラッシュしてしまいますので気をつけてください。

SecondView・ThirdView(中間階層のView)

struct SecondView: View {
    var body: some View {
        VStack {
            NavigationLink(destination: ThirdView()) {
                Text("Forward to Third View.")
            }
        }.navigationBarTitle("Second View")
    }
}

struct ThirdView: View {
    var body: some View {
        VStack {
            NavigationLink(destination: FinalView()) {
                Text("Forward to Final View.")
            }
        }.navigationBarTitle("Third View")
    }
}

ナビゲーションの中間階層のViewでは isDetailLink(false) などの考慮しなければいけないことはなく、特に触れるところはありません。

FinalView(先頭に戻る処理を行うView)

struct FinalView: View {
    
    @EnvironmentObject var envData: EnvironmentData
    
    var body: some View {
        VStack {
            Button("pop to First View") {
                envData.isNavigationActive.wrappedValue = false
            }
        }.navigationBarTitle("Final View")
    }
}

先程 FirstView で宣言した EnvironmentData を同様に宣言します。一見このViewで独自に宣言した変数のように思えますが、これはメモリ上は FirstView で宣言したものと同じものを指しています

ボタンを押下した際に、isNavigationActive を false にすることで FirstViewNavigationLink の遷移が取り消され、結果として FirstView に一気に戻ることになります。

1画面間の遷移だけであれば前回の方法の方がコード量としては少なく済むと思いますが、複数階層ある場合は今回の方法をお勧めします。

他、画面遷移に関する記事については下記もご参照下さい。

【関連記事】