🦅

[SwiftUI]NavigationPathで画面遷移を管理する

2024/12/16に公開

NavigationPathとは

https://developer.apple.com/documentation/swiftui/navigationpath

NavigationPathとはNavigationStackでの画面遷移をパスとして管理できる構造体です。
NavigationPathを活用すると、階層構造の状態を自由に操作できます。
「UIはSwiftUIだけど画面遷移はUIKit」から脱却できるかもしれません。

完成図

この記事で作成するサンプルアプリの完成図です。
これらの階層構造をコードで管理できるようにします。

階層1 階層2 階層3 階層3

階層1. 今日の予定を一覧で確認できる。予定をタップすると詳細画面に遷移する。
階層2. 予定の詳細を確認できる。マップを開く、Webサイトを開くをタップすると各種画面に遷移する。
階層3. Mapで目的地を確認できる。
階層3. Webサイトを確認できる。

NavigationPathの基本的な使い方

ContentView.swift
struct ContentView: View {
    @State private var path = NavigationPath()
    @State private var events: [Event] = [Event.meeting, Event.lunch]

    var body: some View {
        NavigationStack(path: $path) {
            List {
                ForEach(events) { event in
                    NavigationLink(event.title, value: event)
                }
            }
            .navigationTitle("今日の予定")
            .navigationDestination(for: Event.self) { event in
                EventDetailView(event: event)
            }
        }
    }
}

ポイントは3点です。

  1. NavigationStackにNavigationPathを渡す
  2. navigationDestinationで、Viewとデータタイプを紐ずける
  3. NavigationLinkやNavigationPathで画面遷移をする

次の章では上記のコードをベースに紹介、機能の追加をします。

1. NavigationStackにNavigationPathを渡す

https://developer.apple.com/documentation/swiftui/navigationstack/init(path:root:)

NavigationStackに画面遷移のパスを保持するための配列を追加します。
アプリ内で、遷移のフラグとなるデータタイプが単一(navigationDestinationを1つ
しか定義しない)場合は、その型の配列を使用することもできます。

// 複数のデータタイプを扱う場合
@State private var path = NavigationPath()
// Eventのみを扱う場合
@State private var path: [Event] = []
---
NavigationStack(path: $path) {
}

今回は、複数のデータタイプを扱いたいため、NavigationPathを使用します。
NavigationPathは複数の型を1つの配列のように扱える、パスの管理に最適化された構造体です。

2. navigationDestinationで、Viewとデータタイプを紐ずける

https://developer.apple.com/documentation/swiftui/view/navigationdestination(for:destination:)

EventDetailViewにマップ、Web画面に遷移するための処理を追加します。
navigationDestinationの引数forには、画面遷移のフラグとして受け取りたいデータタイプを指定します。

EventDetailView.swift
  enum EventDetailDestination {
      case map
      case web
  }
  
  struct EventDetailView: View {
      let event: Event
  
      var body: some View {
          List {
              Text(event.date, style: .date)
          }
          .navigationTitle(event.title)
+         .navigationDestination(for: EventDetailDestination.self) { destination in
+             switch destination {
+             case .map:
+                 MapView(location: event.location)
+             case .web:
+                 WebView(url: event.url)
+             }
+         }
      }
  }

今回の例では遷移先をEventDetailDestinationというenumで定義しています。
受け取ったEventDetailDestinationの値によって遷移するViewを分岐させています。

navigationDestinationはNavigationStackの中の遅延ロードされないViewであれば、どこにでも追加できます。
画面遷移を実行させる処理の近くに追加すると管理しやすいです。

3. NavigationLinkやNavigationPathで画面遷移をする

https://developer.apple.com/documentation/swiftui/navigationlink/init(_:value:)

マップ、Web画面に遷移するボタンを追加します。
NavigationLinkの引数valueにはnavigationDestinationが期待するデータタイプの値を記載します。

EventDetailView.swift
  struct EventDetailView: View {
      let event: Event
  
      var body: some View {
          List {
              Text(event.date, style: .date)
  
+             Section("Navigation") {
+                 NavigationLink("マップを開く", value: EventDetailDestination.map)
+                 NavigationLink("Webサイトを開く", value: EventDetailDestination.web)
+             }
          }
          .navigationTitle(event.title)
          .navigationDestination(for: EventDetailDestination.self) {
              destination in
              switch destination {
              case .map:
                  MapView(location: event.location)
              case .web:
                  WebView(url: event.url)
              }
          }
      }
  }

今までのNavigationLinkと同じような使い方ができるようになりました。

予定一覧の画面に

  • ミーティングの場所をマップで開く
  • ランチのURLをWebサイトで開く

のような、複数の画面階層に一括で遷移するためのボタンを追加します。
Universal Linksで特定の画面に遷移させたい場合にも有用です。

ContentView.swift
  struct ContentView: View {
      @State private var path = NavigationPath()
      @State private var events: [Event] = [Event.meeting, Event.lunch]
  
      var body: some View {
          NavigationStack(path: $path) {
              List {
                  ForEach(events) { event in
                      NavigationLink(event.title, value: event)
                  }
  
+                 Section("Debug") {
+                     Button("ミーティング / マップ") {
+                         path = NavigationPath([Event.meeting])
+                         path.append(EventDetailDestination.map)
+                     }
+ 
+                     Button("ランチ / Webサイト") {
+                         path = NavigationPath([Event.lunch])
+                         path.append(EventDetailDestination.web)
+                     }
+                 }
              }
              .navigationTitle("今日の予定")
              .navigationDestination(for: Event.self) { event in
                  EventDetailView(event: event)
              }
          }
      }
  }
起動後 ミーティング / マップをタップ

ミーティング / マップをタップすることで、一気にマップ画面に遷移できます。
マップ画面の左上を見るとわかる通り、予定詳細画面の上にマップ画面が載っている状態です。

NavigationPathで画面を遷移する場合は、下記のように配列で上書きしたり、

path = NavigationPath([Event.meeting])

appendを使用して画面を追加します。

path.append(EventDetailDestination.map)

複数の階層からルート画面に戻りたい場合は、下記のように配列を空にします。

path.removeLast(path.count)

必要な場合は、NavigationPathをEnvironmentやBindingを使用して小Viewでも参照することを検討してください。

まとめ

NavigationPathはSwiftUIで画面遷移を管理できる強力なツールです。
従来の方法では提供できなかったリッチなユーザ体験を簡単に提供できます。
この記事が導入の手助けになれば幸いです。

この記事で作成したサンプルアプリはGitHubで公開しています。
https://github.com/RyoDeveloper/NavigationPathSample

Discussion