{"id":48849974,"url":"https://github.com/roy-wonji/tcaflow","last_synced_at":"2026-04-15T08:00:42.918Z","repository":{"id":349701026,"uuid":"1202862530","full_name":"Roy-wonji/TCAFlow","owner":"Roy-wonji","description":"TCA  아키텍쳐 플로우 ","archived":false,"fork":false,"pushed_at":"2026-04-15T06:17:57.000Z","size":267,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-15T07:33:09.913Z","etag":null,"topics":["composable-architecture","coordinator","swift","swiftui","tca"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Roy-wonji.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-06T13:32:34.000Z","updated_at":"2026-04-15T06:18:01.000Z","dependencies_parsed_at":null,"dependency_job_id":"d9cdde7e-868b-45aa-bb19-45e4df1fd218","html_url":"https://github.com/Roy-wonji/TCAFlow","commit_stats":null,"previous_names":["roy-wonji/tcaflow"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/Roy-wonji/TCAFlow","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Roy-wonji%2FTCAFlow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Roy-wonji%2FTCAFlow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Roy-wonji%2FTCAFlow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Roy-wonji%2FTCAFlow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Roy-wonji","download_url":"https://codeload.github.com/Roy-wonji/TCAFlow/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Roy-wonji%2FTCAFlow/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31831849,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-15T07:17:56.427Z","status":"ssl_error","status_checked_at":"2026-04-15T07:17:30.007Z","response_time":63,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["composable-architecture","coordinator","swift","swiftui","tca"],"created_at":"2026-04-15T08:00:22.583Z","updated_at":"2026-04-15T08:00:42.896Z","avatar_url":"https://github.com/Roy-wonji.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TCAFlow\n\n**Swift 6 호환 TCA용 Coordinator-style Navigation 라이브러리**\n\nTCAFlow는 [TCACoordinators](https://github.com/johnpatrickmorgan/TCACoordinators)와 동일한 API를 제공하면서도 **`Hashable` 제약 없이** 사용할 수 있는 navigation 라이브러리입니다.\n\n## ✨ 주요 특징\n\n- 🚀 **Hashable 제약 없음** - `Equatable`만으로 충분\n- 📱 **Native NavigationStack** - iOS 16+의 최신 Navigation API 활용  \n- 🎯 **TCA 전용 설계** - 불필요한 의존성 없음\n- 🏗️ **Nested Coordinator** - 복잡한 플로우 완벽 지원\n- 🔄 **Migration 친화적** - TCACoordinators에서 쉬운 전환\n- ⚡ **Swift 6 호환** - 최신 Swift 기능 활용\n- 🎨 **@FlowCoordinator 매크로** - 보일러플레이트 코드 자동 생성\n\n## 🆚 TCACoordinators와 비교\n\n| 특징 | TCACoordinators | TCAFlow |\n|------|-----------------|---------|\n| **Screen State 제약** | `Hashable` 필수 | `Equatable`만 요구 ✅ |\n| **의존성** | TCA + FlowStacks | TCA만 ✅ |\n| **Navigation API** | FlowStacks 래핑 | Native NavigationStack ✅ |\n| **성능** | 간접 참조 오버헤드 | 직접 참조 최적화 ✅ |\n| **Nested 지원** | 제한적 | 완전 지원 ✅ |\n\n## 📋 요구사항\n\n- **Swift**: 6.0+\n- **TCA**: 1.25.5+\n- **플랫폼**: iOS 16.0+ / macOS 13.0+ / watchOS 9.0+ / tvOS 16.0+\n- **Xcode**: 16.0+ (매크로 지원)\n\n## 📦 설치\n\n### Swift Package Manager\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/Roy-wonji/TCAFlow.git\", from: \"1.0.2\")\n]\n```\n\n```swift\n.target(\n    name: \"App\",\n    dependencies: [\"TCAFlow\"]  // 매크로 자동 포함 ✅\n)\n```\n\n**참고**: TCAFlow 패키지에는 `@FlowCoordinator` 매크로가 자동으로 포함됩니다.\n\n## 🎨 @FlowCoordinator 매크로\n\nTCAFlow는 **`@FlowCoordinator` 매크로**를 제공하여 Coordinator의 보일러플레이트 코드를 자동으로 생성합니다.\n\n### ✨ 매크로를 사용하면 이렇게 간단해집니다!\n\n#### **기존 방식 (수동 작성)**\n```swift\n@Reducer\nstruct AppCoordinator {\n    @ObservableState\n    struct State: Equatable {\n        var routes: [Route\u003cScreen.State\u003e]\n        init() {\n            routes = [.root(.home(.init()), embedInNavigationView: true)]\n        }\n    }\n    \n    @CasePathable\n    enum Action {\n        case router(IndexedRouterActionOf\u003cScreen\u003e)\n    }\n    \n    var body: some Reducer\u003cState, Action\u003e {\n        Reduce { state, action in\n            return handleRoute(state: \u0026state, action: action)\n        }\n        .forEachRoute(\\.routes, action: \\.router)\n    }\n    \n    func handleRoute(state: inout State, action: Action) -\u003e Effect\u003cAction\u003e {\n        // 라우팅 로직...\n    }\n}\n```\n\n#### **💫 매크로 사용 (자동 생성)**\n```swift\n@FlowCoordinator(screen: \"Screen\", navigation: true)\nstruct AppCoordinator {\n    func handleRoute(state: inout State, action: Action) -\u003e Effect\u003cAction\u003e {\n        // 라우팅 로직만 작성!\n        switch action {\n        case .router(.routeAction(_, .home(.detailTapped))):\n            state.routes.push(.detail(.init()))\n            return .none\n        default:\n            return .none\n        }\n    }\n}\n\nextension AppCoordinator {\n    @Reducer\n    enum Screen {\n        case home(HomeFeature)\n        case detail(DetailFeature)\n    }\n}\n\nextension AppCoordinator.Screen.State: Equatable {}\n```\n\n### 🔧 매크로 사용법\n\n#### **방식 1: struct에 직접 적용 (권장)**\n```swift\n@FlowCoordinator(screen: \"Screen\", navigation: true)\nstruct AppCoordinator {\n    // ✅ 자동 생성: State, Action, body\n    \n    func handleRoute(state: inout State, action: Action) -\u003e Effect\u003cAction\u003e {\n        // 라우팅 로직만 작성\n    }\n}\n\nextension AppCoordinator {\n    @Reducer\n    enum Screen {\n        case home(HomeFeature)\n        case detail(DetailFeature)\n    }\n}\n\n// ✅ 자동 생성: Screen.State: Equatable\n```\n\n#### **방식 2: extension에 적용**\n```swift\nstruct AppCoordinator {}\n\n@FlowCoordinator(navigation: true)\nextension AppCoordinator {\n    @Reducer\n    enum Screen {\n        case home(HomeFeature)\n        case detail(DetailFeature)\n    }\n    \n    func handleRoute(state: inout State, action: Action) -\u003e Effect\u003cAction\u003e {\n        // 라우팅 로직\n    }\n}\n```\n\n### 📋 매크로 파라미터\n\n```swift\n@FlowCoordinator(\n    screen: \"Screen\",    // Screen enum 이름 (optional)\n    navigation: true     // root route에 embedInNavigationView 적용 (기본값: true)\n)\n```\n\n- **`screen`**: Screen enum의 이름을 명시적으로 지정\n- **`navigation`**: `true`이면 root route가 NavigationView를 embed\n\n### 🎛️ 커스터마이징\n\n#### **Action에 추가 케이스가 필요한 경우**\n```swift\n@FlowCoordinator(screen: \"Screen\")\nstruct NestedCoordinator {\n    @CasePathable\n    enum Action {\n        case router(IndexedRouterActionOf\u003cScreen\u003e)\n        case backToMain  // ✅ 추가 액션\n        case deepLink(URL)\n    }\n    \n    func handleRoute(state: inout State, action: Action) -\u003e Effect\u003cAction\u003e {\n        switch action {\n        case .backToMain:\n            // 커스텀 로직\n            return .none\n        case .router(let routerAction):\n            // 라우팅 로직\n            return .none\n        default:\n            return .none\n        }\n    }\n}\n```\n\n#### **State 초기화를 커스터마이징하는 경우**\n```swift\n@FlowCoordinator(screen: \"Screen\")\nstruct AppCoordinator {\n    @ObservableState\n    struct State: Equatable {\n        var routes: [Route\u003cScreen.State\u003e]\n        var isLoggedIn: Bool  // ✅ 추가 프로퍼티\n        \n        init(isLoggedIn: Bool = false) {\n            self.isLoggedIn = isLoggedIn\n            self.routes = isLoggedIn\n                ? [.root(.home(.init()), embedInNavigationView: true)]\n                : [.root(.login(.init()), embedInNavigationView: true)]\n        }\n    }\n    \n    // ✅ Action, body는 자동 생성\n}\n```\n\n## 🚀 빠른 시작\n\n### 1️⃣ 기본 Feature 정의\n\n```swift\nimport ComposableArchitecture\nimport TCAFlow\n\n@Reducer\nstruct HomeFeature {\n    @ObservableState\n    struct State: Equatable {  // ✅ Hashable 불필요!\n        var title = \"홈 화면\"\n    }\n    \n    @CasePathable\n    enum Action {\n        case detailButtonTapped\n        case settingsButtonTapped\n    }\n    \n    var body: some ReducerOf\u003cSelf\u003e {\n        Reduce { state, action in\n            switch action {\n            case .detailButtonTapped, .settingsButtonTapped:\n                return .none  // Navigation은 Coordinator에서 처리\n            }\n        }\n    }\n}\n```\n\n### 2️⃣ Coordinator 구현 (매크로 사용)\n\n```swift\n@FlowCoordinator(screen: \"Screen\", navigation: true)\nstruct AppCoordinator {\n    // ✨ State, Action, body는 매크로가 자동 생성!\n    \n    func handleRoute(state: inout State, action: Action) -\u003e Effect\u003cAction\u003e {\n        switch action {\n        // 📱 Navigation 로직만 집중!\n        case .router(.routeAction(_, .home(.detailButtonTapped))):\n            state.routes.push(.detail(.init(title: \"상세 화면\")))\n            return .none\n            \n        case .router(.routeAction(_, .home(.settingsButtonTapped))):\n            state.routes.presentSheet(.settings(.init()))\n            return .none\n            \n        case .router(.routeAction(_, .detail(.backTapped))):\n            state.routes.goBack()\n            return .none\n            \n        default:\n            return .none\n        }\n    }\n}\n\n// 📄 Screen 정의\nextension AppCoordinator {\n    @Reducer\n    enum Screen {\n        case home(HomeFeature)\n        case detail(DetailFeature)\n        case settings(SettingsFeature)\n    }\n}\n\n// ✨ Screen.State: Equatable도 매크로가 자동 생성!\n```\n\n### 3️⃣ View 연결\n\n```swift\nstruct AppCoordinatorView: View {\n    @Bindable var store: StoreOf\u003cAppCoordinator\u003e\n    \n    var body: some View {\n        TCAFlowRouter(store.scope(state: \\.routes, action: \\.router)) { screen in\n            switch screen.case {\n            case .home(let store):\n                HomeView(store: store)\n            case .detail(let store):\n                DetailView(store: store)\n            case .settings(let store):\n                SettingsView(store: store)\n            }\n        }\n    }\n}\n\nstruct HomeView: View {\n    @Bindable var store: StoreOf\u003cHomeFeature\u003e\n    \n    var body: some View {\n        VStack(spacing: 20) {\n            Text(store.title)\n                .font(.largeTitle)\n            \n            Button(\"상세 화면으로\") {\n                store.send(.detailButtonTapped)\n            }\n            \n            Button(\"설정\") {\n                store.send(.settingsButtonTapped)  \n            }\n        }\n        .navigationTitle(\"홈\")\n    }\n}\n```\n\n## 📖 Navigation API 가이드\n\n### 🔄 Push / Pop Navigation\n\n```swift\n// Push (화면 추가)\nstate.routes.push(.detail(.init()))\nstate.routes.push(.settings(.init()))\n\n// Pop (뒤로 가기)\nstate.routes.goBack()           // 1단계 뒤로\nstate.routes.goBack(2)          // 2단계 뒤로\nstate.routes.goBackToRoot()     // 홈으로\n\n// Stack에서만 Pop (presented 화면 유지)\nstate.routes.pop()              // push된 화면만 pop\nstate.routes.popToRoot()        // push된 화면 모두 pop\n```\n\n### 📑 Sheet / FullScreenCover\n\n```swift\n// Sheet 표시\nstate.routes.presentSheet(.settings(.init()))\nstate.routes.presentSheet(.profile(.init()), embedInNavigationView: true)\n\n// FullScreenCover 표시  \nstate.routes.presentCover(.onboarding(.init()))\n\n// Dismiss\nstate.routes.dismiss()          // 최상단 presented 화면 닫기\nstate.routes.dismiss(2)         // 2개 presented 화면 닫기\nstate.routes.dismissAll()       // 모든 presented 화면 닫기\n```\n\n### 🎯 특정 화면으로 이동\n\n```swift\n// 🔙 뒤로 이동 (goBackTo)\nstate.routes.goBackTo(\\.home)         // 홈 화면까지 pop\nstate.routes.goBackTo(\\.profile)      // 프로필 화면까지 pop\n\n// 🎯 스마트 이동 (goTo) - 가장 일반적인 방식\nstate.routes.goTo(.settings(.init()))  // 설정으로 이동 (없으면 새로 생성)\nstate.routes.goTo(.profile(.init()))   // 프로필로 이동 (없으면 새로 생성)\nstate.routes.goTo(.detail(.init()))    // 상세로 이동 (없으면 새로 생성)\n\n// 🏠 이전 화면으로 돌아가기 (특수 용도)\nstate.routes.goTo(\\.home)             // 이전 홈으로 바로 돌아가기\n\n// 🔍 조건부 이동\nstate.routes.goBackTo { route in\n    route.screen.id == \"specific-id\"\n}\n\nstate.routes.goTo { route in\n    route.screen.isTargetScreen\n}\n```\n\n**💡 언제 어떤 방식을 사용할까?**\n\n```swift\n// ✅ 일반적인 경우: 무조건 해당 화면으로 이동\ncase .settingsButtonTapped:\n    state.routes.goTo(.settings(.init()))  // 없으면 새로 생성\n    return .none\n\ncase .profileButtonTapped:\n    state.routes.goTo(.profile(.init(userId: user.id)))\n    return .none\n\n// ✅ 특수한 경우: \"이전 홈으로 돌아가기\" 같은 경우\ncase .backToHomeButtonTapped:\n    state.routes.goTo(\\.home)  // 스택의 홈으로 바로 이동\n    return .none\n```\n\n## 🏗️ Nested Coordinator\n\n복잡한 플로우는 Nested Coordinator로 분리할 수 있습니다.\n\n\u003e **v1.0.2**: 중첩 코디네이터가 부모 `NavigationStack`을 직접 활용합니다.\n\u003e `navigationDestination(isPresented:)` 체이닝으로 **NavigationStack 1개**만 사용하여 네이티브 슬라이드 애니메이션과 스와이프백이 자동 지원됩니다.\n\n```\nAppCoordinator (NavigationStack)\n  ├─ HomeView (root)\n  ├─ [push] → ProfileView          ← _InlineRouteChain(index: 0)\n  │            └─ navigationDestination(isPresented:)\n  │                 └─ [push] → SettingView   ← _InlineRouteChain(index: 1)\n```\n\n```swift\n// 🎯 프로필 전용 Coordinator\n@Reducer\nstruct ProfileCoordinator {\n    @ObservableState\n    struct State: Equatable {\n        var routes: [Route\u003cProfileScreen.State\u003e] = [\n            .root(.profile(.init()), embedInNavigationView: true)\n        ]\n    }\n\n    @CasePathable\n    enum Action {\n        case router(IndexedRouterActionOf\u003cProfileScreen\u003e)\n        case navigation(NavigationAction)\n    }\n\n    enum NavigationAction: Equatable {\n        case presentRoot  // 부모로 돌아가기\n    }\n\n    var body: some Reducer\u003cState, Action\u003e {\n        Reduce { state, action in\n            switch action {\n            case .router(.routeAction(_, .profile(.settingTapped))):\n                state.routes.push(.setting(.init()))\n                return .none\n\n            case .router(.routeAction(_, .setting(.backTapped))):\n                state.routes.goBack()\n                return .none\n\n            default:\n                return .none\n            }\n        }\n        .forEachRoute(\\.routes, action: \\.router)\n    }\n}\n\n// 📱 메인 앱에서 사용\nextension AppCoordinator {\n    @Reducer\n    enum Screen {\n        case home(HomeFeature)\n        case profile(ProfileCoordinator)  // 🎯 Nested Coordinator\n    }\n}\n```\n\n## 🎨 @FlowCoordinator vs 수동 작성\n\n| 특징 | 수동 작성 | @FlowCoordinator 매크로 |\n|------|----------|----------------------|\n| **코드 길이** | ~30줄 | ~10줄 ✅ |\n| **보일러플레이트** | 많음 | 자동 생성 ✅ |\n| **실수 가능성** | 높음 | 낮음 ✅ |\n| **커스터마이징** | 완전 자유 | 일부 제약 |\n| **학습 곡선** | 높음 | 낮음 ✅ |\n\n### 🤔 언제 무엇을 사용할까?\n\n#### **✅ @FlowCoordinator 매크로 사용 권장**\n- 새 프로젝트 시작\n- 간단한 Coordinator\n- 빠른 프로토타이핑\n- 보일러플레이트 줄이고 싶을 때\n\n#### **✅ 수동 작성 권장**  \n- 기존 코드가 많을 때\n- 매우 복잡한 State 초기화\n- Action에 많은 커스텀 케이스 필요\n- 매크로를 학습할 시간이 없을 때\n\n## 💡 실전 팁\n\n### 🔧 매크로 사용 시 팁\n\n```swift\n@FlowCoordinator(screen: \"Screen\", navigation: true)\nstruct AppCoordinator {\n    // ✅ handleRoute 메서드는 필수!\n    func handleRoute(state: inout State, action: Action) -\u003e Effect\u003cAction\u003e {\n        switch action {\n        case .router(let routerAction):\n            return handleRouterAction(state: \u0026state, action: routerAction)\n        default:\n            return .none\n        }\n    }\n    \n    // 🎯 라우터 액션을 별도 메서드로 분리하면 깔끔\n    private func handleRouterAction(\n        state: inout State,\n        action: IndexedRouterActionOf\u003cScreen\u003e\n    ) -\u003e Effect\u003cAction\u003e {\n        switch action {\n        case .routeAction(_, .home(.detailTapped)):\n            state.routes.push(.detail(.init()))\n            return .none\n        // ...\n        }\n    }\n}\n```\n\n### 🔧 라우터 액션 헬퍼 (수동 작성 시)\n\n```swift\nextension AppCoordinator {\n    // 📝 읽기 쉬운 헬퍼 함수\n    private func handleNavigation(\n        state: inout State, \n        action: IndexedRouterActionOf\u003cScreen\u003e\n    ) -\u003e Effect\u003cAction\u003e {\n        switch action {\n        case .routeAction(_, .home(let homeAction)):\n            return handleHomeAction(state: \u0026state, action: homeAction)\n        case .routeAction(_, .detail(let detailAction)):\n            return handleDetailAction(state: \u0026state, action: detailAction)\n        default:\n            return .none\n        }\n    }\n    \n    private func handleHomeAction(\n        state: inout State,\n        action: HomeFeature.Action  \n    ) -\u003e Effect\u003cAction\u003e {\n        switch action {\n        case .detailButtonTapped:\n            state.routes.push(.detail(.init(title: \"상세\")))\n            return .none\n        }\n    }\n}\n```\n\n### 🎨 Route 확장\n\n```swift\nextension Array where Element == Route\u003cAppCoordinator.Screen.State\u003e {\n    var isOnDetailScreen: Bool {\n        last?.screen.case.is(\\.detail) == true\n    }\n    \n    mutating func pushDetailWithId(_ id: String) {\n        push(.detail(.init(id: id)))\n    }\n}\n```\n\n## 🆕 1.1.0 신규 API\n\n### 1️⃣ Sheet Detent 지원\n\n하프시트, detent, drag indicator를 `SheetConfiguration`으로 설정합니다.\n\n```swift\n// 프리셋 사용\nstate.routes.presentSheet(.settings(.init()), configuration: .half)\nstate.routes.presentSheet(.profile(.init()), configuration: .halfAndFull)\n\n// 커스텀 설정\nstate.routes.presentSheet(.filter(.init()), configuration: SheetConfiguration(\n    detents: [.medium, .large],\n    showDragIndicator: true\n))\n```\n\n| 프리셋 | 설명 |\n|--------|------|\n| `.default` | 풀 시트 (`[.large]`) |\n| `.half` | 하프 시트 (`[.medium]`) |\n| `.halfAndFull` | 하프 + 풀 (`[.medium, .large]`) |\n\n---\n\n### 2️⃣ Route Logger\n\n디버그 모드에서 route 변경을 자동 로깅하는 미들웨어입니다.\n\n```swift\nvar body: some Reducer\u003cState, Action\u003e {\n    Reduce { state, action in\n        handleRoute(state: \u0026state, action: action)\n    }\n    .forEachRoute(\\.routes, action: \\.router)\n    .routeLogging(level: .verbose, prefix: \"🏠 [App]\")\n}\n```\n\n| 레벨 | 설명 |\n|------|------|\n| `.minimal` | route 변경 요약만 출력 |\n| `.verbose` | 상세한 route 상태 출력 |\n\n---\n\n### 3️⃣ Route Guard\n\n네비게이션을 인터셉트하여 조건부로 허용/거부합니다.\n\n```swift\n// Guard 정의\nstruct AuthGuard: RouteGuard {\n    func canNavigate\u003cScreen\u003e(\n        from currentRoutes: [Route\u003cScreen\u003e],\n        to newRoutes: [Route\u003cScreen\u003e]\n    ) -\u003e RouteGuardResult {\n        if isAuthenticated {\n            return .allow\n        } else {\n            return .reject(reason: \"로그인이 필요합니다\")\n        }\n    }\n}\n\n// Reducer에 적용\nvar body: some Reducer\u003cState, Action\u003e {\n    Reduce { state, action in\n        handleRoute(state: \u0026state, action: action)\n    }\n    .forEachRoute(\\.routes, action: \\.router)\n    .routeGuard(AuthGuard())\n}\n\n// 수동 체크\nlet canProceed = checkRouteGuard(AuthGuard(), from: state.routes, to: newRoutes)\n```\n\n---\n\n### 4️⃣ DeepLink Helper\n\nURL을 Route로 변환하는 프로토콜 기반 딥링크 처리입니다.\n\n```swift\n// Handler 정의\nstruct AppDeepLinkHandler: DeepLinkHandler {\n    typealias Screen = AppCoordinator.AppScreen.State\n\n    func routes(for url: URL) -\u003e [Route\u003cScreen\u003e]? {\n        guard let host = url.host else { return nil }\n        let params = url.deepLinkParameters\n\n        switch host {\n        case \"detail\":\n            return [\n                .root(.home(.init()), embedInNavigationView: true),\n                .push(.detail(.init(title: params[\"title\"] ?? \"Detail\")))\n            ]\n        default:\n            return nil\n        }\n    }\n}\n\n// 사용\nstate.routes.handleDeepLink(\n    URL(string: \"app://detail?title=Hello\")!,\n    handler: AppDeepLinkHandler(),\n    mode: .replace  // .replace | .keepRoot | .append\n)\n```\n\n| 모드 | 설명 |\n|------|------|\n| `.replace` | 전체 route를 교체 |\n| `.keepRoot` | root를 유지하고 나머지 교체 |\n| `.append` | 기존 route에 추가 |\n\n**URL 헬퍼:**\n```swift\nlet url = URL(string: \"app://detail?title=Hello\u0026id=123\")!\nurl.deepLinkParameters     // [\"title\": \"Hello\", \"id\": \"123\"]\nurl.deepLinkPathComponents // [\"detail\"]\n```\n\n---\n\n### 5️⃣ Tab Coordinator\n\n탭 기반 네비게이션을 위한 전용 라우터입니다.\n\n```swift\n// TabItem 정의\nlet tabs = [\n    TabItem(title: \"홈\", icon: \"house.fill\", tag: 0),\n    TabItem(title: \"프로필\", icon: \"person.fill\", tag: 1),\n    TabItem(title: \"설정\", icon: \"gear\", tag: 2),\n]\n\n// View\nTCAFlowTabRouter(\n    selectedTab: $store.selectedTab,\n    tabs: tabs,\n    onReselect: { tab in store.send(.tabReselected(tab)) }\n) { index in\n    switch index {\n    case 0: HomeCoordinatorView(store: homeStore)\n    case 1: ProfileCoordinatorView(store: profileStore)\n    case 2: SettingsCoordinatorView(store: settingsStore)\n    default: EmptyView()\n    }\n}\n```\n\n**TabCoordinatorState 프로토콜:**\n```swift\nstruct AppState: TabCoordinatorState {\n    var selectedTab: Int = 0\n    mutating func popToRoot(tab: Int) { /* 탭별 root로 이동 */ }\n}\n```\n\n---\n\n### 6️⃣ 전환 애니메이션 커스텀\n\nroute 전환 시 애니메이션을 지정합니다.\n\n```swift\n// View에서 사용\nDetailView(store: store)\n    .routeTransition(.fade(duration: 0.3))\n\nSettingsView(store: store)\n    .routeTransition(.spring(duration: 0.35, bounce: 0.2))\n```\n\n| 애니메이션 | 설명 |\n|-----------|------|\n| `.default` | 시스템 기본 |\n| `.fade(duration:)` | 페이드 인/아웃 |\n| `.spring(duration:bounce:)` | 스프링 |\n| `.easeInOut(duration:)` | ease-in-out |\n| `.none` | 애니메이션 없음 |\n\n---\n\n### 7️⃣ Route 상태 저장/복원\n\n`Codable` Screen과 함께 route 상태를 UserDefaults에 저장/복원합니다.\n\n```swift\n// 저장\nstate.routes.saveRoutes(to: \"app_routes\")\n\n// 복원\nif let saved: [Route\u003cScreen.State\u003e] = .loadRoutes(from: \"app_routes\") {\n    state.routes = saved\n}\n\n// 직접 사용\nRoutePersistence.save(state.routes, key: \"app_routes\")\nlet routes: [Route\u003cScreen.State\u003e]? = RoutePersistence.load(key: \"app_routes\")\nRoutePersistence.clear(key: \"app_routes\")\n```\n\n\u003e ⚠️ Screen.State가 `Codable`을 준수해야 합니다.\n\n---\n\n## 🔄 Migration from TCACoordinators\n\n### 1️⃣ 기본 마이그레이션\n```swift\n// Before (TCACoordinators)\nimport TCACoordinators\nTCARouter(store) { screen in ... }\n\n// After (TCAFlow)  \nimport TCAFlow\nTCAFlowRouter(store) { screen in ... }\n\n// ✅ State에서 Hashable 제거\nstruct MyState: Hashable, Equatable { ... }  // ❌\nstruct MyState: Equatable { ... }            // ✅\n```\n\n### 2️⃣ 매크로로 더 간단하게!\n\n#### **Before (TCACoordinators - 수동 작성)**\n```swift\n@Reducer\nstruct AppCoordinator {\n    @ObservableState\n    struct State: Hashable, Equatable {  // Hashable 필요\n        var routes: [Route\u003cScreen.State\u003e] = [...]\n    }\n    \n    @CasePathable\n    enum Action {\n        case router(IndexedRouterActionOf\u003cScreen\u003e)\n    }\n    \n    var body: some Reducer\u003cState, Action\u003e {\n        Reduce { state, action in\n            // 라우팅 로직...\n        }\n        .forEachRoute(\\.routes, action: \\.router)\n    }\n}\n```\n\n#### **After (TCAFlow - 매크로 사용)**\n```swift\n@FlowCoordinator(screen: \"Screen\", navigation: true)  // 🎨 매크로로 한 줄!\nstruct AppCoordinator {\n    func handleRoute(state: inout State, action: Action) -\u003e Effect\u003cAction\u003e {\n        // 라우팅 로직만 작성하면 끝!\n        switch action {\n        case .router(.routeAction(_, .home(.detailTapped))):\n            state.routes.push(.detail(.init()))\n            return .none\n        default:\n            return .none\n        }\n    }\n}\n```\n\n### 🚀 Migration Steps\n\n1. **Import 변경**: `TCACoordinators` → `TCAFlow`\n2. **Router 변경**: `TCARouter` → `TCAFlowRouter`\n3. **Hashable 제거**: Screen State에서 `Hashable` 삭제\n4. **매크로 적용**: `@FlowCoordinator` 매크로로 보일러플레이트 제거 (선택사항)\n\n## 📚 예제 프로젝트\n\n완전한 예제는 `Example/` 폴더에서 확인하세요:\n\n```\nExample/TCAFlowExamples/\n├── TCAFlowExamplesApp.swift\n├── Coordinators/\n│   ├── DemoCoordinator.swift          # @FlowCoordinator 매크로 사용 예제 🎨\n│   └── DemoCoordinatorView.swift      # 라우터 뷰\n└── Features/\n    ├── Home/                          # 홈 화면 + goTo 예제\n    ├── Flow/                          # 플로우 예제 + goTo 예제  \n    ├── Detail/                        # 상세 화면 + goTo 예제\n    ├── Settings/                      # 설정 화면 + goTo 예제\n    └── Nested/                        # 중첩 코디네이터 예제\n```\n\n**🎨 매크로 사용 예제**: `DemoCoordinator.swift`에서 `@FlowCoordinator` 매크로가 어떻게 보일러플레이트를 줄이는지 확인할 수 있습니다!\n\n### 🔨 예제 빌드\n\n```bash\ncd Example/TCAFlowExamples\nopen TCAFlowExamples.xcodeproj\n```\n\n또는 \n\n```bash\nxcodebuild \\\n    -project Example/TCAFlowExamples/TCAFlowExamples.xcodeproj \\\n    -scheme TCAFlowExamples \\\n    -destination 'generic/platform=iOS Simulator' \\\n    build\n```\n\n## 🤝 기여\n\n기여는 언제나 환영입니다! \n\n1. Fork the repository\n2. Create your feature branch\n3. Make your changes  \n4. Add tests if applicable\n5. Submit a pull request\n\n## 📄 License\n\nMIT License - 자세한 내용은 [LICENSE](LICENSE) 파일을 확인하세요.\n\n---\n\n**TCAFlow**로 더 깔끔하고 유연한 TCA Navigation을 경험해보세요! 🚀","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froy-wonji%2Ftcaflow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Froy-wonji%2Ftcaflow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froy-wonji%2Ftcaflow/lists"}