【SwiftUI】NavigationPathとNavigationStack(path:root:)の使い方!

この記事からわかること

  • SwiftUINavigationStack使い方
  • init(path:root:)の使用方法
  • 孫ビューからルートビューまで一気に戻る方法
  • 画面遷移操作
  • NavigationPath構造体とは?
  • navigationDestination(for:)とは?

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

Swift UIのNavigationStackで使用できるinit(path:root:)NavigationPathの使用方法をまとめていきます。

NavigationStackのinit(path:root:)

公式リファレンス:init(path:root:)

NavigationStack構造体にはinit(path:root:)というイニシャライザが用意されています。このイニシャライザでは引数から変数をバインディングすることで、その変数内に遷移情報をデータとして保持させ、スタック構造のナビゲーション機能をプログラムから管理、操作することが可能になります。

そのため画面遷移時にデータを渡し合うような構造のアプリケーションを開発したい際に役立ちます。

バインディングさせる変数はスタックに依存させたいデータ型(渡す可能性のあるデータ型)の数によって異なります。1種類だけの場合は単純なコレクション型(配列)を複数の種類がある場合はNavigationPath型を使用します。どちらも同名の型違いとしてイニシャライザが定義されています。

@MainActor  init(path: Binding<Data>, @ViewBuilder  root: () -> Root) where Data : MutableCollection, Data : RandomAccessCollection, Data : RangeReplaceableCollection, Data.Element : Hashable
@MainActor  init(path: Binding<NavigationPath>, @ViewBuilder  root: () -> Root) where Data == NavigationPath

また引数rootにはスタックが空の際に表示させるビュー(ルートビューとなる)を渡します。何も渡さなければ設置したNavigationStackを設置したビューになります。

使用するメリット

通常のNavigationStackでは遷移した情報をプログラムから操作することができません。そのため比較的浅いView構造であれば積み上がっているViewを意識することなく使用することができます。

NavigationStack {
    NavigationLink {
        MyNextView()
    } label: {
        Label("MyNextViewへ", systemImage: "arrowshape.turn.up.right.fill")
    }
}

しかし幾層にも重なるような構造の場合は一気にルートビューへ戻したり、2つ前のページへ戻るなどといった複雑な画面遷移を実装したい機会が増えていきます。その際にinit(path:root:)を使用することでより細かな画面遷移を実装できるようになります。

init(path:root:)を使用するメリット

実装例:1つのデータ

まずはシンプルに1つのデータ型に依存した遷移構造を実装してみます。まずNavigationStackにバインディングさせる変数としてシンプルな対象のデータ型の配列を定義します。ここは自身で定義したオリジナルの型でもOKです。

@State  private var path: [任意のデータ型] = []

画面遷移にはNavigationLink(_value:)navigationDestination(for:)メソッドを使用します。NavigationLinkの引数value遷移先に渡したい値を指定します。

NavigationLink("表示用テキスト", value: 対象の値)

navigationDestination(for:)には遷移時に渡すデータ型のタイプを渡し、コールバック関数から該当のデータにアクセスできます。

.navigationDestination(for: データのタイプ) { _ in
     // 遷移先にしたいビュー
}

全体のコード

例として「言語名を文字列で保持し、それぞれの画面に遷移させる」コードを実装してみました。


@State  private var path: [String] = []
@State  private var langs: [String] = ["Swift","Kotlin","Dart"]

var body: some View {
    NavigationStack(path: $path) {
        List{
            ForEach(langs, id:\.self){ lang in
                NavigationLink(lang, value: lang)   
            }
        }
        .navigationDestination(for: String.self) { text in
            ChildView(text: text)
        }            
    }.onChange(of: path) { _ in
        print(path)
    }
}

onChangeを使って画面遷移時のpathの中身の変化を出力してみます。シミュレーターを起動してRootView→Swift→戻る→Kotlin→戻る→Dartのように画面遷移してみると以下のように出力されます。

["Swift"]
[]
["Kotlin"]
[]
["Dart"]

配列の要素を追加して画面遷移させる

NavigationStackに変数をバインディングしている場合はNavigationLinkを使用しなくても対象の変数に対して要素を追加することでも画面遷移を実装できます。例えば以下のようにappendを使用して要素を追加することで追加したデータで画面を遷移させることが可能です。

Button {
    path.append("Objective-C")
} label: {
    Text("path Add Objective-C")
}

配列の要素を削除して画面を戻す

また要素を削除することでも画面を戻すことが可能です。そのためには遷移情報が格納されるpathを子ビューからも操作できるようにバインディングして渡します。そしてのその変数に対してremoveLastメソッドを使用して一番最後の要素(最後の画面遷移履歴)を取り除きます。


.navigationDestination(for: String.self) { text in
  ChildView(path: $path,text: text) // 変更
}

struct ChildView: View {
    @Binding  var path:[String]
    var text:String
    var body: some View {
        VStack{
            Text(text)
            Button {
                path.removeLast()
            } label: {
                Text("path Remove")
            }
        }
        .navigationTitle("ChildView")
    }
}

複数の画面を跨いで遷移する

画面遷移を配列操作で実装できるようになったおかげで配列内の要素を複数取り除くことで画面を跨いで遷移させることが可能になります。まずは以下のような数値の増加とともに画面が遷移するコードがあったとします。

struct TestNavigationView: View {
    
    @State  private var path: [Int] = []
    @State  var num:Int = 0
    
    var body: some View {
        NavigationStack(path: $path) {
            List{
                Text("\(num)")
                Button {
                    num += 1
                    path.append(num)
                } label: {
                    Text("Num Add")
                }
            }
            .navigationDestination(for: Int.self) { num in
                ChildView(path: $path,num: $num)
            }
        }.onChange(of: path) { _ in
            print(path)
        }
    }
}

struct ChildView: View {
    @Binding  var path:[Int]
    @Binding  var num:Int
    var body: some View {
        List{
            Text("\(num)")
            
            Button {
                num += 1
                path.append(num)
            } label: {
                Text("Num Add")
            }
            
            Button {
                num -= 1
                path.removeLast()
            } label: {
                Text("Num Sub")
            }
            Button {
                num = 0
                path.removeAll()
            } label: {
                Text("Back RootView")
            }
        }
        .navigationTitle("ChildView")
        .navigationBarBackButtonHidden(true)
    }
}
【SwiftUI】NavigationPathとNavigationStack(path:root:)の使い方!

そして上記のように画面遷移したとするとpathの中身は以下のように遷移した分だけ配列の要素が追加されていきます。この配列から後2個を取り除いたり、配列自体を空にすることでルートビューまで一気に戻ることができるようになります。

[1]
[1, 2]
[1, 2, 3] // さらに深くへ進めることもできる
[1, 2, 3, 4]

続いて複数のデータ型を扱う場合を見ていきます。そのためにはNavigationPath構造体についてもう少し理解しておきます。

NavigationPath構造体とは?

公式リファレンス:NavigationPath構造体

struct NavigationPath

NavigationPath構造体はスタック構造の遷移情報を管理するためのコレクション形式のデータ型です。コレクション形式なので配列や辞書型などと同じような値の集合になっており、また要素を操作するために必要なappendメソッドやremoveLastメソッド、要素数を取得できるcountプロパティが用意されています。

NavigationPath複数のデータ型を扱いたい時に使用します。そのためNavigationPathでは格納されているデータ型を消去することで異なるタイプを保存できるようになっています。

実装例:複数のデータ

では複数のデータを渡す可能性がある場合を実装してみます。まずは同様にNavigationStackにバインディングさせる変数をNavigationPath型で定義します。

@State  private var path:NavigationPath = NavigationPath()

続いてNavigationStackにバインディングさせたら遷移させたいデータ型ごとにnavigationDestinationを呼び出します。例えばColor構造体とString構造体を扱う場合は以下のように2つ用意します。

.navigationDestination(for: Color.self) { color in
    color.self
}
.navigationDestination(for: String.self) { text in
    ChildView(path:$path,text: text)
}

全体のコードは以下のようになりました。プログラム的には非常に意味のないものになっていますが、ご容赦ください。。。


struct TestNavigationView: View {
    @State  private var path:NavigationPath = NavigationPath()
    @State  private var langs: [String] = ["Swift","Kotlin","Dart"]
    
    var body: some View {
        NavigationStack(path: $path) {
            List{
                ForEach(langs, id:\.self){ lang in
                    NavigationLink(lang, value: lang)
                }
                
                Button {
                    path.append(Color.orange)
                } label: {
                    Text("Display Color")
                }
                
            }
            .navigationDestination(for: Color.self) { color in
                color.self
            }
            .navigationDestination(for: String.self) { text in
                ChildView(path:$path,text: text)
            }
        }.onChange(of: path) { _ in
            print("-------")
            print(path)
        }
    }
}

struct ChildView: View {
    @Binding  var path:NavigationPath
    var text:String
    var body: some View {
        List{
            Text(text)
            
            Button {
                path.append(Color.cyan)
            } label: {
                Text("Add Color")
            }
            
            Button {
                path.removeLast()
            } label: {
                Text("remove")
            }
            Button {
                path.append("Objective-C")
            } label: {
                Text("Add Objective")
            }
        }
        .navigationTitle("ChildView")
        .navigationBarBackButtonHidden(true)
    }
}
【SwiftUI】NavigationPathとNavigationStack(path:root:)の使い方!

上記のように「Swift」→「Objective-C」→「Color.cyan」と変化させた場合はpathの中身は以下のようになります。

NavigationPath(_items: SwiftUI.NavigationPath.(unknown context at $10a2ef200).Representation.eager([SwiftUI.(unknown context at $10a2eef88).CodableItemBox<Swift.String>]), subsequentItems: [], iterationIndex: 0)
-------
NavigationPath(_items: SwiftUI.NavigationPath.(unknown context at $10a2ef200).Representation.eager([SwiftUI.(unknown context at $10a2eef88).CodableItemBox<Swift.String>, SwiftUI.(unknown context at $10a2eef88).CodableItemBox<Swift.String>]), subsequentItems: [], iterationIndex: 0)
-------
NavigationPath(_items: SwiftUI.NavigationPath.(unknown context at $10a2ef200).Representation.eager([SwiftUI.(unknown context at $10a2eef88).CodableItemBox<Swift.String>, SwiftUI.(unknown context at $10a2eef88).CodableItemBox<Swift.String>, SwiftUI.(unknown context at $10a2eee78).ItemBox<SwiftUI.Color>]), subsequentItems: [], iterationIndex: 0)

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index