本記事では、iOS16+で新しく登場したナビゲーション管理機能である「NavigationStack」の機能と使い方についてまとめていきます。

SwiftUIにおいて、従来のナビゲーション管理機能であったNavigationViewがiOS16からは非推薦となり、将来的にはNavigationStackへの移行が必要となりました。

これを見ている中には、まだ使ったことがない方もいるかと思います。この機会にNavigationStackに触れてみましょう!

本記事のソースコード

本記事で使用しているサンプルコードはGitHubにて公開されています。

コード内容の確認にご利用ください🍎

GitHubを見に行く>>

[ 本記事はこんな人におすすめ ]

・iOS16+のナビゲーション管理方法について知りたい

・非推薦となったNavigationViewの代替えとなる機能を知りたい

・SwiftUIでアプリ開発をしている

概要

従来のナビゲーション遷移管理方法であったNavigationViewがiOS16以降は非推薦となり、新たな管理方法としてNavigationStackが登場しました。

NavigationStackを用いることで今までと同じような使用感でナビゲーション周りの実装が可能ですが、新たな機能として「配列によるナビゲーションの状態管理」が追加されました。

本記事では、特にこの新機能に重点を置いて解説していきます。

【配列によるナビゲーションの状態管理の図】

上の図に関しての詳しい解説は、後の項目「配列によるナビゲーション管理のメリット」にて記述しています。

基本的な書き方と機能

定義する場所

NavigationStackの定義場所は、NavigationViewと同様にルート階層のビューに定義します。

				
					struct ContentView: View {

    var body: some View {

        NavigationStack {

            // ...

        }
    }
}
				
			

NavigationLinkの活用

NavigationStackの管理下であれば、従来のナビゲーション管理と同じような使用感でNavigationLinkによる画面遷移が可能です。

ナビゲーション修飾子(modifier)

NavigationTitleNavigationBarTitleDisplayModeなどのナビゲーション系統の修飾子も、従来の記述で使うことができます。

子ビュー側での定義は必要ない

親ビュー側でNavigationStackが定義されていれば、子ビューでの定義は必要ありません。こちらもNavigationViewと同じですね。

navigationDestination(新機能)

navigationDestinationとは

NavigationStack用として登場した修飾子で、指定したデータ型のフラグを受け取ってナビゲーション遷移を発火させることができます。
				
					/// 値のフラグを受け取ってナビゲーション遷移を発火することができる
/// 引数「for:」には、受け取る値のデータ型を指定
.navigationDestination(for: <受け取るデータ型>.self) { value in

    /// この中に遷移先のビューを定義
    /// 受け取った値(value)は遷移先に使用ことができる

}
				
			

データ型はString、Intをはじめstruct(構造体)やenum(列挙体)なども渡すことができます。

NavigationLinkとの組み合わせ

NavigationLinkにデータフラグを持たせることで、タップ遷移時にnavigationDestinationにフラグを渡して遷移コントロールができます。

以下の例では、Colorデータ型をフラグ指定したnavigationDestinationを定義し、NavigationLink(value:)からのColorデータフラグを受け取って、遷移を発火しています。

NavigationLinkからColorデータを受け取って遷移

渡されるデータと遷移先決定の流れ

NavigationLink ➡︎ navigationDestinationのデータの流れは以下の図のようになります。

データが渡り遷移が発火されるまでの流れ

NavigationLinkが持つ「value:」のデータが、どこに渡ってきて遷移が発火されているのかがイメージできると全体が把握しやすいと思います。

※受け取るデータの型とフラグ指定したデータ型が異なる場合、エラーは発生しませんが遷移が実行されないため注意してください。

navigationDestinationの複数定義

navigationDestinationは複数の定義ができます。

例えば以下のように、NavigationLinkから渡ってくるデータがそれぞれ異なるデータ型であるような場合、複数のデータ受け皿を用意することで遷移先を散らすことも可能です。

				
					// structを渡すにはHashable準拠が必要
struct Person: Hashable {
    var name: String
    var age: Int
}

// ...

List {
    NavigationLink("Stringデータを渡す", value: "CodeCandy")
    NavigationLink("Intデータを渡す", value: 100)
    NavigationLink("structデータを渡す", value: Person(name: "スウィフト太郎", age: 18))
}
/// Stringを受け取って遷移
.navigationDestination(for: String.self) { stringValue in

    VStack(spacing: 30) {
        Text("Stringデータを受け取りました")
        Text("データ: \(stringValue)")
    }
    .font(.title)

}
/// Intを受け取って遷移
.navigationDestination(for: Int.self) { intValue in

    VStack(spacing: 30) {
        Text("Intデータを受け取りました")
        Text("データ: \(intValue)")
    }
    .font(.title)

}
/// structを受け取って遷移
.navigationDestination(for: Person.self) { structValue in

    VStack(spacing: 30) {
        Text("structデータを受け取りました")
        Text("名前: \(structValue.name)")
        Text("年齢: \(structValue.age)")
    }
    .font(.title)
}
				
			

受け取るデータの型をnavigationDestinationが検知して、遷移先を振り分けていることがわかりますね。

いずれの実装も、NavigationStackの管理内で定義することに注意してください。

配列を用いたナビゲーション管理の実装

ここからはNavigationStackの新機能であり大きな特徴である、ナビゲーションの状態管理の役割を配列プロパティに渡すことができる」という点について解説していきます。

先の項目で述べた基本的な実装に加えて、配列を用いたナビゲーション管理機能をコードに追加していきます。

まずは全体の実装コードから見ていきましょう。

コードは以下のようになります。

				
					struct ContentView: View {

    // ❶ナビゲーションの状態を管理する配列パス
    @State private var navigatePath: [String] = []

    var body: some View {

        NavigationStack(path: $navigatePath) { // ⬅︎ ❷配列パスと紐付ける

            VStack {
            
            // View...
            
            }
            // ❸配列パスに追加された値が⬇︎に渡ってくる
            //   ここに遷移先のビューを定義
            .navigationDestination(for: String.self) { value in
                Text("受け取った値: \(value)")
            }
        }
    }
}
				
			

このように実装することで、配列に値を追加すると、値の追加を検知してナビゲーション遷移が発火します。

実際にボタンアクションを使って、配列にappendメソッドで値を追加してみると、以下のように動作します。

配列パスに文字列"CodeCandy"を渡して遷移を発火した様子

配列への値追加と同時に、ナビゲーション遷移の発火が確認できますね。

では、上記の実装においてのポイントを見ていきましょう。

配列管理の実装におけるポイント

① ナビゲーションの状態を管理する配列パス

				
					@State private var navigatePath: [String] = []
				
			

@Stateでラップされた配列を用意します。

・データ型は「String」「Int」をはじめ、「Color」や「enum(列挙体)」なども配列パスのデータ型として使用可能です。

・このプロパティがナビゲーション遷移状態の管理役となり、遷移先のビューと紐付いたパスが保持される場所となります。

・この配列に値を追加するたびに、ナビゲーション遷移も重なっていきます。つまり、「配列内の要素の数」と「遷移の回数」は常に一対となります。この辺りの振る舞いについては後の項目「配列によるナビゲーション管理のメリット」にて詳しく解説します。

② NavigationStackと配列パスの紐付け(バインディング)

				
					NavigationStack(path: $navigatePath) { // ...
				
			

①で用意した配列パスをNavigationStackの引数「path:」にバインディングします。

③ navigationDestinationに遷移先のビューを定義

				
					.navigationDestination(for: String.self) { value in

    // 遷移先のビューを定義...
}
				
			

NavigationStackの配下にnavigationDestination修飾子を定義して、内部で遷移先のビューを定義します。この修飾子がナビゲーション遷移の発火役となります。

・クロージャ引数valueには、配列パスに追加した値が渡ってきます。この値を用いて遷移先に値を渡したり、条件分岐によるビューの出し分けが可能です。

.navigationDestinationの引数「for:」で指定しているデータ型は、配列パスに設定したデータ型です。配列パスが[Int]型であれば、こちらも「Int.self」となります。

配列によるナビゲーション管理のメリット

先の項目でも述べたように、NavigationStackの大きな特徴として「配列を用いたナビゲーション遷移の状態管理」ができます。

では、配列で管理することによってどのようなメリットがあるのでしょうか?

例えば以下のように、複数回のナビゲーション遷移が重なるアプリ画面は多く見られますね。このような遷移は「ドリルダウン方式の画面遷移」とも呼ばれ、従来のナビゲーション管理方法だとシステム制御が複雑になりがちな実装パターンです。

こちらのケースを用いて、配列管理のメリットを見ていきましょう。

実装ケースのコード概要

上記の実装ではenum+switch文による遷移ビューの条件分岐を用いています。

				
					// 配列パスとして使う列挙型
enum SamplePath {
    case pathA, pathB, pathC, pathD
}

// enumをデータ型に指定した配列パス
@State private var navigatePath: [SamplePath] = []
				
			

配列パスにenumのケースを追加することで、.navigationDestinationの中でenumのケースを条件判定し、紐付いたビューに遷移が発火するという処理の流れです。

遷移先の子ビューからも配列パスを操作できるように、各ビューに配列の参照を渡しています。

				
					// enumのケースを用いて遷移先のビューを分岐
.navigationDestination(for: SamplePath.self) { value in

    switch value {

    case .pathA:
        PathAView(path: $navigatePath)

    case .pathB:
        PathBView(path: $navigatePath)

    case .pathC:
        PathCView(path: $navigatePath)

    case .pathD:
        PathDView(path: $navigatePath)

    }
}
				
			

メリット1. 画面遷移の状態が把握しやすい

NavigationStackは、ナビゲーション遷移による画面の構成を配列の中で一元管理するので、全体の遷移状態が見通しやすいというメリットがあります。

以下の図は、先に述べた実装において、「pathA > pathB > pathC」の順で配列パスに要素を追加した際の構成イメージ図です。

ビューは配列パスに追加した順に重なっていきます。

配列パス内の要素と遷移画面は紐付いており、「配列の要素数」と遷移ビューの枚数」は一対の関係になっています。

配列内に一連の遷移状態が全て管理されているので、全体の見通しが良く、システム制御による遷移コントロールがしやすくなるというメリットがあります。

メリット2. 配列操作のように遷移をコントロールできる

配列内に画面遷移の状態が一元管理されていることによって、配列データを操作するような感覚でビューの追加や破棄が可能になります。

これが非常に直感的な操作が可能で、従来のナビゲーション管理だと実装が複雑になりがちであった「複数遷移先のビューから特定の遷移元ビューまで戻る」といった動作も、容易にコントロールができます。

では、画面遷移の操作方法を見ていきましょう。

配列による遷移操作方法

次のビューに遷移する(append)

appendメソッドは配列の最後尾に要素を追加するメソッドです。これにより、次の画面への遷移を発火させることができます。

例えば、先の状態から配列にenumのケース「pathD」を追加すれば、さらにD画面へ遷移します。

複数のビューにまとめて遷移する

append(contentsOf:)メソッドで複数の要素を追加すれば、一度で複数のビューにまとめて遷移することも可能です。

前のビューに戻る(removeLast)

removeLastメソッドは配列の最後尾の要素を削除するメソッドです。これにより、現在の画面が破棄され、一つ前の画面へ戻ることができます。

画面左上のBackボタンや、dismiss()と同様の動作ですね。

特定の遷移元ビューまで戻る

removeLast(2)のように整数値を渡すことで、指定した数の要素を最後尾から順に削除することができます。これにより、特定の遷移元ビューまで戻るような動作を直感的に実装できます。

⚠️ 配列内の要素を超えた数を指定するとアプリが落ちるので注意です。

最初のビューまで一気に戻る

最初の画面まで一気に戻りたい場合は、removeLast(<配列パス>.count)を使います。

配列内の要素が全て削除され、最初の画面へジャンプします。

このように、配列操作の感覚で柔軟に画面遷移をコントロールすることができるのが、NavigationStackによるナビゲーション配列管理の大きなメリットです。

まとめ

以上、iOS16+でのナビゲーション管理機能NavigationStackについて、特に新機能である「配列を用いたナビゲーション管理」に重点を置いてご紹介しました!

本記事のポイントをまとめておきます。

配列操作の感覚でナビゲーション遷移をコントロールできる

ナビゲーションの状態を配列内で一元管理できる

従来のような方法でのナビゲーション管理も可能

実際のアプリ開発現場ではiOS15以前のサポートを切ることはまだ少し先になるかとは思いますが、個人開発などでiOS16+のサポートで進めている方は、ぜひこの機会にNavigationStackを使ってみてください👍

参考になりましたら幸いです。

\  SHARE  /

Twitter
Facebook
Email

✏️ アプリ開発が学べる勉強会を開催中! 

CodeCandyではアプリ開発を学ぶための勉強会を定期開催しています。
学習する習慣を身につけたい、他の参加者と作業したい、アプリ開発の基本をマスターしたい、という方のために無料で学べる勉強会です。
グループにメンバー登録して頂くと、イベント開催時にメールで通知されます。

▶️グループのメンバーとして参加する

徹底した基礎学習からマスターするiPhoneアプリ開発集中オンライン講座開講!

本書「iPhoneアプリ開発集中講座」を執筆している現役エンジニア講師陣が直接に指導!
基礎、課題実習で実践力を鍛えて、オリジナルアプリ公開までチャレンジ!
充実した転職支援もあるので、エンジニアへ転職したい人にもおすすめです!

まずは、現役エンジニアに相談できる無料相談をご利用ください。

\ Official /

By 中川 賢亮

2022年2月よりSwift学習を始め、4月からiOSアプリ開発オンラインスクール「CodeCandy」にてアプリ開発を学ぶ。 2023年10月に個人開発アプリ「unico」をリリース。現在はアプリの機能アップデートをしながら、スクール運営の技術ブログの執筆や、出版書籍の入稿チェック・デバッグにも携わる。