複数階層のナビゲーション

アプリでは複数の階層を持つナビゲーション構造が必要になることがあります。NavigationStack で深い階層のナビゲーションを実装する方法を解説します。

階層的なナビゲーション

NavigationLink を連続して使用することで、複数階層のナビゲーションを構築できます。

struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                NavigationLink("カテゴリA") {
                    CategoryView(name: "カテゴリA")
                }
                NavigationLink("カテゴリB") {
                    CategoryView(name: "カテゴリB")
                }
            }
            .navigationTitle("ホーム")
        }
    }
}

struct CategoryView: View {
    let name: String
    
    var body: some View {
        List {
            NavigationLink("サブカテゴリ1") {
                SubCategoryView(name: "サブカテゴリ1")
            }
            NavigationLink("サブカテゴリ2") {
                SubCategoryView(name: "サブカテゴリ2")
            }
        }
        .navigationTitle(name)
    }
}

struct SubCategoryView: View {
    let name: String
    
    var body: some View {
        List {
            NavigationLink("アイテム1") {
                ItemDetailView(name: "アイテム1")
            }
            NavigationLink("アイテム2") {
                ItemDetailView(name: "アイテム2")
            }
        }
        .navigationTitle(name)
    }
}

struct ItemDetailView: View {
    let name: String
    
    var body: some View {
        Text("\(name)の詳細")
            .navigationTitle(name)
    }
}

navigationDestination を使った階層管理

値ベースのナビゲーションを使うと、遷移先の定義を一箇所にまとめられます。

enum Route: Hashable {
    case category(String)
    case subCategory(String)
    case item(String)
}

struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                NavigationLink("カテゴリA", value: Route.category("A"))
                NavigationLink("カテゴリB", value: Route.category("B"))
            }
            .navigationDestination(for: Route.self) { route in
                switch route {
                case .category(let name):
                    CategoryRouteView(category: name)
                case .subCategory(let name):
                    SubCategoryRouteView(subCategory: name)
                case .item(let name):
                    ItemRouteView(item: name)
                }
            }
            .navigationTitle("ホーム")
        }
    }
}

階層間でのデータ受け渡し

深い階層にデータを渡す方法はいくつかあります。

直接渡す

各画面のイニシャライザでデータを受け取る。シンプルだが、階層が深いとバケツリレーになる。

Environment

@EnvironmentObject や @Environment でデータを共有。どの階層からもアクセス可能。

path で状態管理

NavigationPath に必要な情報をすべて含めて管理する。

// EnvironmentObject を使った例
class AppState: ObservableObject {
    @Published var selectedCategory: String?
    @Published var cart: [String] = []
}

struct ContentView: View {
    @StateObject private var appState = AppState()
    
    var body: some View {
        NavigationStack {
            CategoryListView()
        }
        .environmentObject(appState)
    }
}

struct ItemDetailView: View {
    let item: String
    @EnvironmentObject var appState: AppState
    
    var body: some View {
        VStack {
            Text(item)
            Button("カートに追加") {
                appState.cart.append(item)
            }
        }
    }
}

任意の階層に直接遷移

path を使えば、途中の画面をスキップして深い階層に直接遷移できます。

struct ContentView: View {
    @State private var path: [Route] = []
    
    var body: some View {
        NavigationStack(path: $path) {
            VStack {
                Button("アイテム詳細に直接遷移") {
                    // 階層を一気に構築
                    path = [
                        .category("A"),
                        .subCategory("Sub1"),
                        .item("Item1")
                    ]
                }
            }
            .navigationDestination(for: Route.self) { route in
                // ... 遷移先の定義
            }
        }
    }
}
通常の階層遷移

ユーザーが1画面ずつ進んでいく。戻るボタンで1つずつ戻る。

ディープリンク的な遷移

path を操作して任意の階層に直接ジャンプ。通知からの遷移などに便利。

複数階層のナビゲーションでは、画面間の依存関係を整理し、適切なデータ管理方法を選択することが重要です。