値ベースのナビゲーション(navigationDestination)

iOS 16 で導入された値ベースのナビゲーションは、navigationDestination を使ってデータの型に基づいた画面遷移を実現します。従来の NavigationLink よりも柔軟で、プログラム的な制御が容易になりました。

従来の NavigationLink との違い

従来の方法では、NavigationLink に直接遷移先のビューを指定していました。

// 従来の方法
NavigationLink("詳細") {
    DetailView(item: item)
}

値ベースのナビゲーションでは、NavigationLink に値を渡し、その値の型に対応する遷移先を navigationDestination で定義します。

// 値ベースの方法
NavigationLink("詳細", value: item)

// 別の場所で遷移先を定義
.navigationDestination(for: Item.self) { item in
    DetailView(item: item)
}

基本的な使い方

struct Item: Identifiable, Hashable {
    let id = UUID()
    var name: String
}

struct ContentView: View {
    let items = [
        Item(name: "りんご"),
        Item(name: "みかん"),
        Item(name: "バナナ")
    ]
    
    var body: some View {
        NavigationStack {
            List(items) { item in
                NavigationLink(item.name, value: item)
            }
            .navigationDestination(for: Item.self) { item in
                Text("\(item.name)の詳細画面")
                    .navigationTitle(item.name)
            }
            .navigationTitle("フルーツ")
        }
    }
}

値の型は Hashable に準拠している必要があります。

複数の型に対応

異なる型に対して、それぞれ異なる遷移先を定義できます。

struct User: Hashable {
    var name: String
}

struct Product: Hashable {
    var title: String
}

struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                Section("ユーザー") {
                    NavigationLink("田中太郎", value: User(name: "田中太郎"))
                    NavigationLink("山田花子", value: User(name: "山田花子"))
                }
                
                Section("商品") {
                    NavigationLink("iPhone", value: Product(title: "iPhone"))
                    NavigationLink("MacBook", value: Product(title: "MacBook"))
                }
            }
            .navigationDestination(for: User.self) { user in
                UserDetailView(user: user)
            }
            .navigationDestination(for: Product.self) { product in
                ProductDetailView(product: product)
            }
        }
    }
}
型による分岐

navigationDestination は型ごとに定義できるため、同じリスト内で異なる種類のデータを扱える。

Hashable 必須

値として渡す型は Hashable に準拠している必要がある。Identifiable だけでは不十分。

遅延評価

遷移が実際に発生するまで、遷移先のビューは生成されない。

列挙型を使ったパターン

画面遷移のパターンを列挙型で定義すると、管理しやすくなります。

enum Destination: Hashable {
    case settings
    case profile(userId: Int)
    case article(articleId: Int)
}

struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                NavigationLink("設定", value: Destination.settings)
                NavigationLink("プロフィール", value: Destination.profile(userId: 123))
                NavigationLink("記事", value: Destination.article(articleId: 456))
            }
            .navigationDestination(for: Destination.self) { destination in
                switch destination {
                case .settings:
                    SettingsView()
                case .profile(let userId):
                    ProfileView(userId: userId)
                case .article(let articleId):
                    ArticleView(articleId: articleId)
                }
            }
        }
    }
}
従来の NavigationLink

遷移先を直接指定。シンプルだが、プログラム制御が難しい。

値ベースのナビゲーション

値を渡して型で遷移先を決定。path による制御が可能。

値ベースのナビゲーションは、複雑なナビゲーション構造を持つアプリで特に威力を発揮します。次の記事では、path を使ったプログラム的な画面遷移について解説します。