💁‍♂️

【SwiftUI】NavigationStackでナビゲーションパスを保持して手動で制御する

2023/04/09に公開

はじめに

SwiftUIにおけるナビゲーション管理の方法について、以下2つの方法を紹介します。

  1. NavigationLinkに遷移先のViewを紐づけて直接的にナビゲーションを実行する
  2. NavigationLinkにデータを紐づけて間接的にナビゲーションを実行する

この記事の主張

  • 方法1: NavigationLinkに遷移先のViewを紐づけて直接的にナビゲーションを実行
  • 方法2: NavigationLinkにデータを紐づけて間接的にナビゲーションを実行する
  • 方法2はナビゲーションパスを記録することが容易になる
  • 方法2でナビゲーションを手動制御する

本題

方法1: NavigationLinkに遷移先のViewを紐づけて直接的にナビゲーションを実行

まずは、NavigationLinkに遷移先のViewを紐づけて直接的にナビゲーションを実行する方法についてです。

import SwiftUI

struct ParentView1: View {
    var body: some View {
        NavigationStack {
            List {
                NavigationLink("ChildView1") {
                    ChildView(data: "1")
                }
                NavigationLink("ChildView2") {
                    ChildView(data: "2")
                }
                NavigationLink("ChildView3") {
                    ChildView(data: "3")
                }
            }
            .navigationTitle("ParentView")
        }
    }
}

方法2: NavigationLinkにデータを紐づけて間接的にナビゲーションを実行する

次に、NavigationLinkにデータを紐づける方法です。

ポイントは以下のとおりです。

  1. 各NavigationLinkにデータ値1 2 3を割り当ててリンクを作成
  2. navigationDestination(for:destination:)ビュー修飾子で遷移先ビューにデータを関連づける
import SwiftUI

struct ParentView: View {
    
    var body: some View {
        NavigationStack {
            List {
                NavigationLink("ChildView1", value: "1")
                NavigationLink("ChildView2", value: "2")
                NavigationLink("ChildView3", value: "3")
            }
            .navigationTitle("Parent View")
            .navigationDestination(for: String.self) { value in
                ChildView(data: value)
            }
        }
    }
}

方法2のメリットは、以下のとおりです。

  • データ値をNavigationLinkに割り当てているため、ナビゲーションパスを記録できるようになる
  • 結果的にナビゲーションを手動制御できるようになる

次の節では、ナビゲーションパスの記録方法とナビゲーションの手動制御について解説します。

方法2はナビゲーションパスを記録することが容易になる

方法2を前提として、ナビゲーションパスを記録する方法です。

ポイントは以下のとおりです。

  1. NavigationLinkのvalueを格納する配列navigationPathを用意する(Stateプロパティラッパーをつける)
  2. NavigationStack(path:root:)でナビゲーションを管理する
  3. navigationDestination(for:destination:)で遷移先のビューを指定する
import SwiftUI

struct ParentView: View {
    
    @State private var navigationPath: [String] = []
    
    var body: some View {
        NavigationStack(path: $navigationPath) {
            List {
                NavigationLink("ChildView1", value: "1")
                NavigationLink("ChildView2", value: "2")
                NavigationLink("ChildView3", value: "3")
            }
            .navigationTitle("Parent View")
            .navigationDestination(for: String.self) { value in
                ChildView(data: value)
            }
        }
    }
}

こうすることで、遷移時にNavigationLinkのデータがナビゲーションパスの末尾に記録されます。

[] //親ビュー
["1"] //子ビューのChildView1に遷移したとき

ナビゲーションパスを配列で管理できるようになると、その変更を監視してナビゲーションを手動で制御可能になります。

方法2でナビゲーションを手動制御する

方法2を前提として、ナビゲーションを手動制御する方法です。

ポイントは以下のとおりです。

  1. ナビゲーションパスの配列navigatonPathに遷移先のデータを手動でスタックする
  2. NavigationStack(path:root:)でナビゲーションを管理する
  3. navigationDestination(for:destination:)で遷移先のビューを指定する

以下の例では、NavigationLinkの代わりにButtonを使用しています。
ボタン押下時にナビゲーションパスの配列に遷移先のデータを手動でスタックしています。

import SwiftUI

struct ParentView: View {
    
    @State private var navigationPath: [String] = []
    
    var body: some View {
        NavigationStack(path: $navigationPath) {
            List {
//                NavigationLink("ChildView1", value: "1")
                Button("ChildView1") {
                    showChildView(value: "1")
                }
//                NavigationLink("ChildView2", value: "2")
                Button("ChildView2") {
                    showChildView(value: "2")
                }
//                NavigationLink("ChildView3", value: "3")
                Button("ChildView3") {
                    showChildView(value: "3")
                }
            }
            .navigationTitle("Parent View")
            .navigationDestination(for: String.self) { value in
                ChildView(data: value, navigationPath: $navigationPath)
            }
        }
    }
    private func showChildView(value: String) {
        navigationPath.append(value)
    }
}

struct ChildView: View {
    
    let data: String
    @Binding var navigationPath: [String]
    
    var body: some View {
        Button("トップに戻る") {
            navigationPath.removeAll()
        }
        .navigationTitle("\("ChildView" + data)")
    }
}

子ビューのChildViewでは、「トップに戻る」ボタンでナビゲーションパスの要素をクリアすることで、ナビゲーションの状態を変更しています。

まとめ

  • 方法1: NavigationLinkに遷移先のViewを紐づけて直接的にナビゲーションを実行
  • 方法2: NavigationLinkにデータを紐づけて間接的にナビゲーションを実行する
  • 方法2はナビゲーションパスを記録することが容易になる
  • 方法2でナビゲーションを手動制御する

Discussion