SwiftUIのTCAとNavigationStackを使って画面遷移をする

## 概要

TCAでiOS16から使えるNavigationStackを使って画面遷移します。
TCAでNavigationStackを使って遷移する方法がわからなかったので。実験的に実装してみました。
Point Freeから推奨されたやり方がアップデートされるか、サンプルコードで提示されるとは思いますが。。



![TCANavigationStack.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/44713/4cc1c287-a6a7-5122-676c-144687246cd3.gif)

https://github.com/yosuke1985/TCANavigationStack

- ParkListViewからParkDetailViewへ遷移する

``` swift
    @BindableState var path = NavigationPath()
```

### ポイント

- Stateの中でRouteを定義し、そのenum Routeに従って画面遷移するようにする。

- NavigationPathには、そのenum Routeを追加・削除していくことで画面遷移していく

bindingActionとしてpathを定義すること
ActionにBindableActionを準拠させる。
StateにNavigationPathを@BindableStateで定義する。

## ActionとState

``` swift
enum ParkListAction: BindableAction, Equatable {
    case binding(BindingAction<ParkListState>)
    case goToParkDetailView(Park.ID)
    case parkDetailAction(ParkDetailAction)
}

struct ParkListState: Equatable, Identifiable {
    var id: UUID
    @BindableState var path = NavigationPath()
    var parkList: IdentifiedArrayOf<Park> = [.init(name: "Yoyogi Park"),
                                             .init(name: "Merican Park"),
                                             .init(name: "Rikyu Park")]
    var parkDetailState: ParkDetailState?
    enum Route: Equatable, Hashable {
        case parkDetailView(Park.ID)
        case parkDetailRoute(ParkDetailState.Route)
    }
}
```

parkListはIdentifiedArrayで定義しているので[id: UUID]で取得することができる。

## Reducer

``` swift
let parkListReducer = Reducer<ParkListState, ParkListAction, ParkListEnvironment>
    { state, action, environment in
        switch action {
        case .goToParkDetailView(let id):
            guard let park = state.parkList[id: id] else { return .none }
            state.parkDetailState = .init(park: park)
            state.path.append(ParkListState.Route.parkDetailView(park.id))
            return .none
        case .binding(\.$path):
            if state.path.isEmpty {
                state.parkDetailState = nil
            }
            return .none
        case .parkDetailAction(.goToTextView):
            state.path.append(ParkListState.Route.parkDetailRoute(.textView))
            return .none
        case .parkDetailAction(.goBack):
            state.path.removeLast()
            return .none
        default:
            return .none
        }
    }.binding()
    .combined(with:
                parkDetailReducer
        .optional()
        .pullback(state: \.parkDetailState,
                  action: /ParkListAction.parkDetailAction,
                  environment: { _ in .init() })
    )
```

navigationDestinationにenumのRouteが渡ってくるようにすることで、
switch文でRouteによってどの画面を表示させるかを記述する

``` swift
struct ParkListView: View {
    let store: Store<ParkListState, ParkListAction>

    var body: some View {
        WithViewStore(store) { viewStore in
            NavigationStack(path: viewStore.binding(\.$path)) {
                VStack(spacing: 10) {
                    ForEach(viewStore.parkList) { park in
                        Button {
                            viewStore.send(.goToParkDetailView(park.id))
                        } label: {
                            Text(park.name)
                        }
                    }
                }
                .navigationTitle("Park List View")
                .navigationDestination(for: ParkListState.Route.self) { route in
                    switch route {
                    case .parkDetailView:
                        IfLetStore(
                            store.scope(
                                state: \.parkDetailState,
                                action: ParkListAction.parkDetailAction
                            ),
                            then: ParkDetailView.init(store:)
                        )
                    case .parkDetailRoute(.textView):
                        Text("Hello World!")
                    }
                }
            }
        }
    }
}
```

## あとがき

NavigationPathにenum で定義したrouteを追加・削除していくことで遷移することができました。
TCAとNavigationStackでゴリゴリに作ったことがあるわけではないので、これよりもっと良い書き方があるかもしれません。