SwiftUI4, iOS16以降で使用できる NavigationStackについて軽く調査した

## まえがき

WWDC2022にてNavigationStackが新しく発表されました。
iOS16からNavigationViewはdeprecatedで、NavigationStackを使わないといけないです。

SwiftUIは画面遷移周りがよくない・・・・！と、
SwiftUIを実戦投入していても、画面遷移まわりはUIKitを使用しているという話をチラホラききますし、
私が参画している現場においても画面遷移はUIKitで書いてます。

NavigationStackをどんなものか気になっていたので、軽く調査してみました。

## Case 1, NavigationStackとNavigationLinkのみを使って遷移

NavigationStackの中にNavigationLinkを配置して遷移するだけ。
NavigationStackをdeprecatedされたNavigationViewのようです。

``` swift
struct NavigationStackSample1: View {
    let parkList: [Park] = [.init(name: "メリケンパーク"), .init(name: "万博記念公園"), .init(name: "奈良公園"), .init(name: "代々木公園")]
    var body: some View {
        NavigationStack {
            List(parkList) { park in
                NavigationLink(park.name) {
                    ParkDetailView(park: park)
                }
            }
            .navigationTitle("公園リスト")
        }
    }
}
```

## Case 2, NavigationStackとNavigationLinkとnavigationDestinationを使った例

``` swift
struct NavigationStackSample2: View {
    let parkList: [Park] =  [.init(name: "メリケンパーク"), .init(name: "万博記念公園"), .init(name: "奈良公園"), .init(name: "代々木公園")]
   let fruitsList: [Fruits] =  [.init(name: "スイカ"), .init(name: "みかん"), .init(name: "りんご"), .init(name: "バナナ")]
    var body: some View {
        NavigationStack {
            List(parkList) { park in
                NavigationLink(park.name, value: park)
            }
            .navigationTitle("Park List")
            .navigationDestination(for: Park.self) { park in
                ParkDetailView(park: park)
            }
            .navigationDestination(for: Fruits.self) { fruits in
                FruitsDetailView(fruits: fruits)
            }
        }
    }
}
```

``` swift
func navigationDestination<D, C>(
    for data: D.Type,
    destination: @escaping (D) -> C
) -> some View where D : Hashable, C : View
```

このnavigationDestinationはD.Typeを引数にとります。

``` swift
NavigationLink(park.name, value: park)
```

``` swift
   .navigationDestination(for: Park.self) { park in
        ParkDetailView(park: park)
    }
```

この例だと、
NavigationLinkのvalueにPark型のオブジェクトが渡ってくると、
このParkDetailViewに遷移してくださいという意味になります。
NavigationLinkのvalueに渡ってくる型によって、どの画面に遷移するかを決定しているので、
型の数だけnavigationDestinationを生やせばよいです。

## Case 3, Routerをenumにして遷移する例

StructだけでなくEnumでも良いです。
enumでcaseごとに定義しておいて、
navigationDestination内で遷移を定義してもよいです。

navigationDestinationのクロージャの引数としてenumのRouteが渡ってくるので、
それをswitchでどの画面に遷移するか定義すればよいです。

``` swift
enum Route: Hashable {
    case park(Park)
    case fruits(Fruits)
}

NavigationStack {
    List {
        Section("Fruits List") {
            ForEach(fruitsList) { fruits in
                NavigationLink(value: Route.fruits(fruits)) {
                    Text(fruits.name)
                }
            }
        }

        Section("Park List") {
            ForEach(parkList) { park in
                NavigationLink(value: Route.park(park)) {
                    Text(park.name)
                }
            }
        }
    }
    .navigationTitle("Fruits Park")
    .navigationDestination(for: Route.self) { route in
        switch route {
        case let .fruits(fruits):
            FruitsDetailView(fruits: fruits)
        case let .park(park):
            ParkDetailView(park: park)
        }
    }
}
```

## Case 4, NavigationStackのpathを使った遷移

![Stackのイメージ](https://3.bp.blogspot.com/-9HsLN4ISjn8/UzKmzOJvjeI/AAAAAAAAeow/Dsd5teoBHos/s800/osara_yamadumi.png)

NavigationStackは文字通りStack、つまり、Viewを上に重ねていっております。

pathに構造体Parkが遷移されればNavigationStackのpathで
遷移を操作していく。

そのPathからremoveLastすれば１つ前に戻り、
removeLast(2)すれば、２つ前に戻る。

以下のshowFirstParkを実行すれば、

奈良公園の遷移して、代々木公園に遷移する。

``` swift
@State private var path: [Park] = []

var body: some View {
    NavigationStack(path: $path) {
        List(parkList) { park in
            NavigationLink(value: park) {
                Text(park.name)
            }
        }
        .navigationDestination(for: Park.self) { park in
            ParkDetailView(park: park)
        }
        .navigationTitle("公園リスト")
    }
    .onChange(of: path) { newValue in
        print(newValue)
    }
    .onAppear {
        showFirstPark()
    }
}

func showFirstPark() {
    path = [.init(name: "奈良公園"), .init(name: "代々木公園")]
}
```

## Case 5, NavigationPathのpathを使った遷移

pathには、非同期処理できてBindingできる@Stateないしは@Publishedのものを設定する。

pathに設定するのは、画面遷移するときに使用する構造体やEnumよいです

``` swift
enum Route: Hashable {
    case park(Park)
    case fruits(Fruits)
}

final class RouterEnum: ObservableObject {
    @Published var path: [Route] = []
}
```

routerはNavigationPathでも良いし、enumにして定義してもよいです。

NavigationPathというのもあり、
前の例だとpathは構造体Parkに限定していましたが、
navigationDestinationで定義されているものであれば、遷移が可能となります。

@Environmentプロパティーラッパーを使えば、子の環境すべてに伝搬できるので、

``` Swift
final class RouterNavigationPath: ObservableObject {
    @Published var path =  NavigationPath()
}

struct NavigationStackSample6: View {
    @EnvironmentObject var router: RouterNavigationPath
    let parkList: [Park] = [Park(name: "メリケンパーク"), .init(name: "万博記念公園"), .init(name: "奈良公園"), .init(name: "代々木公園")]

    var body: some View {
        NavigationStack(path: $router.path) {
            List(parkList) { park in
                NavigationLink(value: park) {
                    Text(park.name)
                }
            }
            .navigationDestination(for: Park.self) { park in
                ParkDetailView2(park: park)
            }
            .navigationTitle("公園リスト")
        }
        .environmentObject(router)
    }
}
```

遷移先の画面にも
    @EnvironmentObject var router: RouterNavigationPath
と書くことで、遷移先のParkDetailView2のrouterからremoveLast()すれば、
戻ることができます。

``` swift
struct ParkDetailView2: View {
    let park: Park
    @EnvironmentObject var router: RouterNavigationPath

    var body: some View {
        Group {
            Text("\(park.name)にようこそ")
            Button("戻る"){
                router.path.removeLast()
            }
        }
        .onAppear {
            print("router", router)
        }
    }
}
```

## あとがき

かなり自由度が上がっていて書きやすそうなのも良いですね、
がっつりNavigationStackを使って書いてみようとおもいます。

## リファレンス

Youtubeのコードまとめて　調査する

First Look at the Navigation API in SwiftUI
<https://www.youtube.com/watch?v=3Ur6kUStKRo&t>

SwiftUI 4: Navigation Stacks (WWDC 2022) – iOS
<https://www.youtube.com/watch?v=dZdHJQT7Z4Y>

New Programmatic Navigation In SwiftUI 4.0 - Xcode 13 - WWDC 2022
<https://www.youtube.com/watch?v=rm7-DkqKZCo&t>

NavigationStack
<https://developer.apple.com/documentation/swiftui/navigationstack>

A First Look at SwiftUI NavigationStack, NavigationPath, and “navigationDestination”
<https://betterprogramming.pub/a-first-look-at-swiftui-navigationstack-navigationpath-and-navigationdestination-3a9bbb300e08>

Mastering NavigationStack in SwiftUI. Navigator Pattern.
<https://swiftwithmajid.com/2022/06/15/mastering-navigationstack-in-swiftui-navigator-pattern/>