### Automatic Effect Cancellation Example Source: https://context7.com/johnpatrickmorgan/tcacoordinators/llms.txt This example shows an async effect in `NumberDetail` that is automatically cancelled if the user swipes back before the 3-second delay completes. The effect is run with a delay, and if the screen is dismissed, the `send` operation is cancelled. ```swift @Reducer struct NumberDetail { @Dependency(\mainQueue) var mainQueue var body: some ReducerOf { Reduce { state, action in switch action { case .incrementAfterDelayTapped: return .run { send in try await mainQueue.sleep(for: .seconds(3)) await send(.incrementTapped) // cancelled if screen dismissed first } case .incrementTapped: state.number += 1 return .none default: return .none } } } } ``` -------------------------------- ### Default Effect Cancellation with forEachRoute Source: https://context7.com/johnpatrickmorgan/tcacoordinators/llms.txt By default, effects started by a screen are automatically cancelled when that screen is popped or dismissed. This behavior is managed by `forEachRoute` which wraps effects with `CancellationIdentity`. ```swift .forEachRoute(\routes, action: \.router) ``` -------------------------------- ### Implement Coordinator Logic with forEachRoute Source: https://github.com/johnpatrickmorgan/tcacoordinators/blob/main/README.md Implement the coordinator's reducer logic to handle screen presentations and dismissals. Use `forEachRoute` to apply the `Screen` reducer to each route in the navigation stack. ```swift @Reducer struct Coordinator { ... var body: some ReducerOf { Reduce { state, action in switch action { case .router(.routeAction(_, .home(.startTapped))): state.routes.presentSheet(.numbersList(.init(numbers: Array(0 ..< 4))), embedInNavigationView: true) case .router(.routeAction(_, .numbersList(.numberSelected(let number)))): state.routes.push(.numberDetail(.init(number: number))) case .router(.routeAction(_, .numberDetail(.showDouble(let number)))): state.routes.presentSheet(.numberDetail(.init(number: number * 2))) case .router(.routeAction(_, .numberDetail(.goBackTapped))): state.routes.goBack() default: break } return .none } .forEachRoute(\.routes, action: \.router) } } ``` -------------------------------- ### Route Navigation Convenience Methods Source: https://context7.com/johnpatrickmorgan/tcacoordinators/llms.txt Use these extension methods on Array and IdentifiedArray within a coordinator's Reduce closure to manage navigation stacks and presented screens. They provide ergonomic helpers for common navigation mutations like pushing, presenting, going back, popping, and dismissing. ```swift // Inside Coordinator.body's Reduce closure: var body: some ReducerOf { Reduce { state, action in switch action { // --- Push / Present --- case .showList: // Push onto the current NavigationStack state.routes.push(.numbersList(.init(numbers: [1, 2, 3]))) case .showSheet: // Present as a sheet (optionally embed in its own NavigationStack) state.routes.presentSheet(.numberDetail(.init(number: 42)), withNavigation: true) case .showCover: // Present as a full-screen cover (iOS only) state.routes.presentCover(.numberDetail(.init(number: 99))) // --- Go back --- case .back: state.routes.goBack() // go back 1 screen (push or present) case .backTwo: state.routes.goBack(2) // go back N screens case .backToRoot: state.routes.goBackToRoot() // back to the very first screen case .backToList: // Back to the most recent screen matching a case path state.routes.goBackTo(\ .numbersList) // --- Pop (push-only navigation) --- case .pop: state.routes.pop() // pop 1 pushed screen case .popToRoot: state.routes.popToRoot() // pop all pushed screens in current nav stack case .popToCurrentNavRoot: state.routes.popToCurrentNavigationRoot() // --- Dismiss (presented screens only) --- case .dismissSheet: state.routes.dismiss() // dismiss 1 presented layer case .dismissAll: state.routes.dismissAll() // dismiss all presented layers default: break } return .none } .forEachRoute(\ .routes, action: \ .router) } ``` -------------------------------- ### Route Factory Methods Source: https://context7.com/johnpatrickmorgan/tcacoordinators/llms.txt Use these static factory methods on Route to wrap screen states with display information. They are useful for initializing the root screen, subsequent screens, or for deep-linking. ```swift // Root screen (the first screen; can have a NavigationStack embedded) let root: Route = .root(.home(.init()), withNavigation: true) // Subsequent screens let pushed: Route = .push(.numberDetail(.init(number: 1))) let sheet: Route = .sheet(.numbersList(.init(numbers: [1,2,3])), withNavigation: true) let cover: Route = .cover(.numberDetail(.init(number: 99))) // iOS only // Use directly in state initialisation @ObservableState struct State: Equatable { var routes: [Route] = [ .root(.home(.init()), withNavigation: true) ] } // Deep-link: push straight to a specific screen at launch static func deepLink(to number: Int) -> State { State(routes: [ .root(.home(.init()), withNavigation: true), .push(.numbersList(.init(numbers: Array(0..<10)))), .push(.numberDetail(.init(number: number))) ]) } ``` -------------------------------- ### Composing Child Coordinators with Scope and Reduce Source: https://context7.com/johnpatrickmorgan/tcacoordinators/llms.txt Demonstrates how a parent reducer can manage child coordinators using Scope and handle actions for switching between them based on application state. The parent view switches between child coordinator views. ```swift struct GameApp { @ObservableState struct State: Equatable { var logIn: LogInCoordinator.State = .initialState var game: GameCoordinator.State = .initialState() var isLoggedIn: Bool = false } enum Action { case logIn(LogInCoordinator.Action) case game(GameCoordinator.Action) } var body: some ReducerOf { Scope(state: \.logIn, action: \.logIn) { LogInCoordinator() } Scope(state: \.game, action: \.game) { GameCoordinator() } Reduce { state, action in switch action { case let .logIn(.router(.routeAction(_, .logIn(.logInTapped(name))))): state.game = .initialState(playerName: name) state.isLoggedIn = true case .game(.router(.routeAction(_, .game(.logOutButtonTapped)))): state.logIn = .initialState state.isLoggedIn = false default: break } return .none } } } struct AppCoordinatorView: View { let store: StoreOf var body: some View { WithPerceptionTracking { if store.isLoggedIn { GameCoordinatorView(store: store.scope(state: \.game, action: \.game)) .transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading))) } else { LogInCoordinatorView(store: store.scope(state: \.logIn, action: \.logIn)) .transition(.asymmetric(insertion: .move(edge: .trailing), removal: .move(edge: .leading))) } } .animation(.default, value: store.isLoggedIn) } } // NOTE: Child coordinators must be *presented* (sheet/cover), not pushed, // when embedded inside a parent coordinator's routes array. ``` -------------------------------- ### Create a CoordinatorView Source: https://github.com/johnpatrickmorgan/tcacoordinators/blob/main/README.md A CoordinatorView uses TCARouter to manage a nested list of screen views. It takes a scoped store and a closure to create views for each screen. A switch statement is used to map screen cases to their respective views. ```swift struct CoordinatorView: View { let store: StoreOf var body: some View { TCARouter(store.scope(state: \.routes, action: \.router)) { screen in switch screen.case { case let .home(store): HomeView(store: store) case let .numbersList(store): NumbersListView(store: store) case let .numberDetail(store): NumberDetailView(store: store) } } } } ``` -------------------------------- ### Coordinator State with Route Array Source: https://github.com/johnpatrickmorgan/tcacoordinators/blob/main/README.md The coordinator's state should include an array of `Route` to manage the navigation stack. Appending a new screen state to this array triggers its presentation. ```swift @Reducer struct Coordinator { @ObservableState struct State: Equatable { var routes: [Route] } ... } ``` -------------------------------- ### Combine Coordinator and Screen Reducers with forEachRoute Source: https://context7.com/johnpatrickmorgan/tcacoordinators/llms.txt Use `forEachRoute` to combine a coordinator's reducer with screen-specific reducers. This ensures actions are routed correctly and effects are cancelled when screens are dismissed. To opt out of automatic effect cancellation, set `cancellationId` to `nil`. ```swift @Reducer struct Coordinator { @ObservableState struct State: Equatable { var routes: [Route] = [.root(.home(.init()), withNavigation: true)] } enum Action { case router(IndexedRouterActionOf) } var body: some ReducerOf { Reduce { state, action in switch action { // Navigate on screen actions case .router(.routeAction(_, .home(.startTapped))): state.routes.presentSheet(.numbersList(.init(numbers: Array(0..<4))), withNavigation: true) case let .router(.routeAction(_, .numbersList(.numberSelected(number)))): state.routes.push(.numberDetail(.init(number: number))) case let .router(.routeAction(_, .numberDetail(.showDouble(number)))): state.routes.presentSheet(.numberDetail(.init(number: number * 2)), withNavigation: true) case .router(.routeAction(_, .numberDetail(.goBackTapped))): state.routes.goBack() case .router(.routeAction(_, .numberDetail(.goBackToRootTapped))): state.routes.goBackToRoot() default: break } return .none } // Wire in the Screen reducer for every route; auto-cancel effects on dismiss .forEachRoute(\.routes, action: \.router) } } // Opt out of automatic effect cancellation: // .forEachRoute(\.routes, action: \.router, cancellationId: nil) ``` -------------------------------- ### Update TCARouter Initialization Source: https://github.com/johnpatrickmorgan/tcacoordinators/blob/main/Docs/Migration/Migrating from 0.8.md When instantiating TCARouter in a view, scope the store using the same keypath and case path as used in the coordinator's reducer. ```swift TCARouter(store.scope(state: \.routes, action: \.router)) { screen in ``` -------------------------------- ### Coordinator Action with IndexedRouterAction Source: https://github.com/johnpatrickmorgan/tcacoordinators/blob/main/README.md The coordinator's action should include a case for `IndexedRouterActionOf` to dispatch screen actions correctly and manage updates to the routes array, such as handling back navigation. ```swift @Reducer struct Coordinator { ... enum Action { case router(IndexedRouterActionOf) } ... } ``` -------------------------------- ### Define Screen Reducer with @Reducer Macro Source: https://github.com/johnpatrickmorgan/tcacoordinators/blob/main/README.md Define an enum reducer to manage different screen states within a navigation flow. The `@Reducer` macro with `.hashable` state simplifies state management for various screens. ```swift @Reducer(state: .hashable) enum Screen { case home(Home) case numbersList(NumbersList) case numberDetail(NumberDetail) } ``` -------------------------------- ### Individual Screen Reducers Source: https://context7.com/johnpatrickmorgan/tcacoordinators/llms.txt Define individual screen reducers that are unaware of the navigation flow. These reducers manage their own state and actions. ```swift @Reducer struct Home { struct State: Hashable { let id = UUID() } enum Action { case startTapped } } ``` ```swift @Reducer struct NumbersList { @ObservableState struct State: Hashable { let id = UUID() let numbers: [Int] } enum Action { case numberSelected(Int) } } ``` ```swift @Reducer struct NumberDetail { @ObservableState struct State: Hashable { let id = UUID() var number: Int } enum Action { case incrementTapped case showDouble(Int) case goBackTapped case goBackToRootTapped } } ``` -------------------------------- ### `@Reducer enum Screen` Source: https://context7.com/johnpatrickmorgan/tcacoordinators/llms.txt Defines the reducer for aggregating all possible screens within a navigation flow. Each case in the enum represents a distinct screen with its own reducer. ```APIDOC ## `@Reducer enum Screen` — Define the screen reducer An enum reducer (using TCA's `@Reducer` macro with `state: .hashable`) that aggregates all possible screens in a navigation flow. Each case holds the reducer for one screen; TCA generates a combined `State` enum and `Action` enum automatically. ```swift import ComposableArchitecture @Reducer(state: .hashable) enum Screen { case home(Home) case numbersList(NumbersList) case numberDetail(NumberDetail) } // Individual screen reducers — each is unaware of navigation @Reducer struct Home { struct State: Hashable { let id = UUID() } enum Action { case startTapped } } @Reducer struct NumbersList { @ObservableState struct State: Hashable { let id = UUID() let numbers: [Int] } enum Action { case numberSelected(Int) } } @Reducer struct NumberDetail { @ObservableState struct State: Hashable { let id = UUID() var number: Int } enum Action { case incrementTapped case showDouble(Int) case goBackTapped case goBackToRootTapped } } ``` ``` -------------------------------- ### Define Screen Reducer Enum Source: https://context7.com/johnpatrickmorgan/tcacoordinators/llms.txt Use the `@Reducer` macro with `.hashable` state to create an enum that aggregates all possible screens in a navigation flow. Each case holds the reducer for a specific screen. ```swift import ComposableArchitecture @Reducer(state: .hashable) enum Screen { case home(Home) case numbersList(NumbersList) case numberDetail(NumberDetail) } ``` -------------------------------- ### IndexedCoordinator with Array Routes Source: https://context7.com/johnpatrickmorgan/tcacoordinators/llms.txt Define an IndexedCoordinator using an array of `Route` for managing navigation. Screens are identified by their position in the array, which is suitable for push/pop operations. ```swift // Indexed (array-based) — screens identified by their position in the array @Reducer struct IndexedCoordinator { @ObservableState struct State: Equatable { var routes: [Route] = [.root(.home(.init()), withNavigation: true)] } enum Action { case router(IndexedRouterActionOf) // Int-keyed } } ``` -------------------------------- ### Identifiable Conformance for Screen State Source: https://context7.com/johnpatrickmorgan/tcacoordinators/llms.txt Ensure screen states conform to `Identifiable` by providing a unique `id` for each screen state. This is required when using `IdentifiedCoordinator`. ```swift extension Screen.State: Identifiable { var id: UUID { switch self { case let .home(s): s.id case let .numbersList(s): s.id case let .numberDetail(s): s.id } } } ``` -------------------------------- ### Add Casepath to Coordinator Action Type Source: https://github.com/johnpatrickmorgan/tcacoordinators/blob/main/Docs/Migration/Migrating from 0.8.md Use this Swift code to manually add a casepath to an action type that formerly conformed to `IdentifiedRouterAction`. This is a simpler, short-term migration strategy. ```swift // Quick update for an action that formerly conformed to `IdentifiedRouterAction`. enum Action: CasePathable { case updateRoutes(IdentifiedArrayOf>) case routeAction(Screen.State.ID, action: Screen.Action) static var allCasePaths = AllCasePaths() struct AllCasePaths { var router: AnyCasePath> { AnyCasePath { routerAction in switch routerAction { case let .routeAction(id, action): return .routeAction(id, action: action) case let .updateRoutes(newRoutes): return .updateRoutes(IdentifiedArray(uniqueElements: newRoutes)) } } extract: { action in switch action { case let .routeAction(id, action: action): return .routeAction(id: id, action: action) case let .updateRoutes(newRoutes): return .updateRoutes(newRoutes.elements) } } } } } ``` -------------------------------- ### Migrate IndexedCoordinator Reducer Source: https://github.com/johnpatrickmorgan/tcacoordinators/blob/main/Docs/Migration/Migrating from 0.8.md This diff shows the changes required to migrate an IndexedCoordinator reducer to version 0.9. It includes removing deprecated protocols, adding the @Reducer macro, and updating action cases and route handling to use case paths. ```diff struct IndexedCoordinatorView: View { let store: StoreOf var body: some View { - TCARouter(store) { screen in + TCARouter(store.scope(state: \.routes, action: \.router)) { screen in SwitchStore(screen) { screen { case .home: CaseLet( \Screen.State.home, action: Screen.Action.home, then: HomeView.init ) case .numbersList: CaseLet( \Screen.State.numbersList, action: Screen.Action.numbersList, then: NumbersListView.init ) case .numberDetail: CaseLet( \Screen.State.numberDetail, action: Screen.Action.numberDetail, then: NumberDetailView.init ) } } } } } +@Reducer struct IndexedCoordinator { - struct State: Equatable, IndexedRouterState { + struct State: Equatable { var routes: [Route] } - enum Action: IndexedRouterAction { + enum Action { - case routeAction(Int, action: Screen.Action) - case updateRoutes([Route]) + case router(IndexedRouterActionOf) } var body: some ReducerOf { Reduce { state, action in switch action { - case .routeAction(_, .home(.startTapped)): + case .router(.routeAction(_, .home(.startTapped))): state.routes.presentSheet(.numbersList(.init(numbers: Array(0 ..< 4))), embedInNavigationView: true) - case let .routeAction(_, .numbersList(.numberSelected(number))): + case let .router(.routeAction(_, .numbersList(.numberSelected(number)))): state.routes.push(.numberDetail(.init(number: number))) - case .routeAction(_, .numberDetail(.goBackTapped)): + case .router(.routeAction(_, .numberDetail(.goBackTapped))): state.routes.goBack() - case .routeAction(_, .numberDetail(.goBackToRootTapped)): + case .router(.routeAction(_, .numberDetail(.goBackToRootTapped))): - return .routeWithDelaysIfUnsupported(state.routes) { + return .routeWithDelaysIfUnsupported(state.routes, action: \.router) { $0.goBackToRoot() } default: break } return .none } - .forEachRoute { + .forEachRoute(\. outes, action: \.router) { Screen() } } } ``` -------------------------------- ### `IndexedRouterActionOf` / `IdentifiedRouterActionOf` Source: https://context7.com/johnpatrickmorgan/tcacoordinators/llms.txt Type aliases for `RouterAction` that specify how screens are identified within the navigation flow. `IndexedRouterActionOf` uses integer indices, while `IdentifiedRouterActionOf` uses `Identifiable.ID`. ```APIDOC ## `IndexedRouterActionOf` / `IdentifiedRouterActionOf` — Router action type aliases Type aliases for `RouterAction` that declare how screens are identified. `IndexedRouterActionOf` uses `Int` array indices (safe for push/pop); `IdentifiedRouterActionOf` uses the screen state's `Identifiable.ID` and requires `IdentifiedArrayOf>` as the routes collection. ```swift // Indexed (array-based) — screens identified by their position in the array @Reducer struct IndexedCoordinator { @ObservableState struct State: Equatable { var routes: [Route] = [.root(.home(.init()), withNavigation: true)] } enum Action { case router(IndexedRouterActionOf) // Int-keyed } } // Identified (IdentifiedArray-based) — screens identified by their own ID extension Screen.State: Identifiable { var id: UUID { switch self { case let .home(s): s.id case let .numbersList(s): s.id case let .numberDetail(s): s.id } } } @Reducer struct IdentifiedCoordinator { @ObservableState struct State: Equatable { var routes: IdentifiedArrayOf> = [.root(.home(.init()), withNavigation: true)] } enum Action { case router(IdentifiedRouterActionOf) // UUID-keyed } } ``` ``` -------------------------------- ### TCARouter for Coordinator Navigation in SwiftUI Source: https://context7.com/johnpatrickmorgan/tcacoordinators/llms.txt Use `TCARouter` in your SwiftUI view to manage a navigation stack based on a store of routes. It takes a scoped store and a view builder closure to render the appropriate view for each screen state. ```swift struct CoordinatorView: View { let store: StoreOf var body: some View { // Scope down to just the routes slice + router action TCARouter(store.scope(state: \.routes, action: \.router)) { screen in switch screen.case { case let .home(store): HomeView(store: store) case let .numbersList(store): NumbersListView(store: store) case let .numberDetail(store): NumberDetailView(store: store) } } } } ``` ```swift // IdentifiedArray variant — pass the IdentifiedArray store directly struct IdentifiedCoordinatorView: View { @State var store: StoreOf var body: some View { TCARouter(store.scope(state: \.routes, action: \.router)) { screen in switch screen.case { case let .home(store): HomeView(store: store) case let .numbersList(store): NumbersListView(store: store) case let .numberDetail(store):NumberDetailView(store: store) } } } } ``` -------------------------------- ### IdentifiedCoordinator with IdentifiedArray Routes Source: https://context7.com/johnpatrickmorgan/tcacoordinators/llms.txt Define an IdentifiedCoordinator using `IdentifiedArrayOf` for managing navigation. Screens are identified by their `Identifiable.ID`, requiring the screen state to conform to `Identifiable`. ```swift // Identified (IdentifiedArray-based) — screens identified by their own ID @Reducer struct IdentifiedCoordinator { @ObservableState struct State: Equatable { var routes: IdentifiedArrayOf> = [.root(.home(.init()), withNavigation: true)] } enum Action { case router(IdentifiedRouterActionOf) // UUID-keyed } } ``` -------------------------------- ### Custom Effect Cancellation Key Source: https://context7.com/johnpatrickmorgan/tcacoordinators/llms.txt Use a custom `cancellationIdType` with `forEachRoute` when multiple coordinators share screens. This allows for specific cancellation keys, preventing unintended cancellation of effects across different coordinator instances. ```swift .forEachRoute(\routes, action: \.router, cancellationIdType: MyCoordinator.self) ``` -------------------------------- ### Opting Out of Effect Cancellation Source: https://context7.com/johnpatrickmorgan/tcacoordinators/llms.txt To prevent automatic cancellation of effects when a screen is dismissed, set the `cancellationId` parameter to `nil` in `forEachRoute`. Effects will continue running even after the screen is popped. ```swift .forEachRoute(\routes, action: \.router, cancellationId: nil) ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.