TCAのチュートリアルのTodoをモジュール化してみた

# 環境
- Xcode 
    - 14.2
- Swift
    -  5.7.2
- The Composable Architecture
    - 0.52.0
- mac OS 
    - 13.1

# はじめに
フォルダ構成はisowordsを参考にしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179037/2a908934-b07b-ba70-caee-520b00dff1ec.png)

# 手順
1. ライブラリーの作成
    1. File>New>Project>Swift Packageでライブラリーの作成(TodoFeature)
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179037/7fa988de-9961-c494-075d-d69ba29c8b75.png)
1. Package.swiftを編集する
    ```swift:Package.swift
    // swift-tools-version: 5.7
    // The swift-tools-version declares the minimum version of Swift required to build this package.
    
    import PackageDescription
    
    let package = Package(
        name: "Todos",
        platforms: [
            .iOS(.v15),
            .macOS(.v12),
            .tvOS(.v15),
            .watchOS(.v8),
        ],
        products: [
            // Products define the executables and libraries a package produces, and make them visible to other packages.
            .library(
                name: "TodosFeature",
                targets: ["TodosFeature"]),
        ],
        dependencies: [
            // Dependencies declare other packages that this package depends on.
            // .package(url: /* package url */, from: "1.0.0"),
            .package(url: "https://github.com/pointfreeco/swift-composable-architecture", branch: "main")
        ],
        targets: [
            // Targets are the basic building blocks of a package. A target can define a module or a test suite.
            // Targets can depend on other targets in this package, and on products in packages this package depends on.
            .target(
                name: "TodosFeature",
                dependencies: [
                    .product(name: "ComposableArchitecture", package: "swift-composable-architecture")
                ]),
            .testTarget(
                name: "TodosFeatureTests",
                dependencies: ["TodosFeature"]),
        ]
    )
    ```
1. TodosFeature.swiftを編集する
    <details>
    <summary>こちらをクリック</summary>
    
    ```swift:TodosFeature.swift
    import ComposableArchitecture
    import SwiftUI
    
    public struct Todos: ReducerProtocol {
        public struct State: Equatable {
            var todos: [Todo] = []
    
            public init(todos: [Todo]) {
                self.todos = todos
            }
        }
    
        public enum Action {
            case todoCheckboxTapped(index: Int)
            case todoTextFieldChanged(index: Int, text: String)
        }
    
        public init() {}
    
        public func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
            switch action {
                case .todoCheckboxTapped(index: let index):
                    state.todos[index].isComplete.toggle()
                    return .none
                case .todoTextFieldChanged(index: let index, text: let text):
                    state.todos[index].description = text
                    return .none
            }
        }
    }
    
    public struct Todo: Equatable, Identifiable {
        public var description = ""
        public let id: UUID
        public var isComplete = false
    
        public init(description: String = "", id: UUID = UUID(), isComplete: Bool = false) {
            self.description = description
            self.id = id
            self.isComplete = isComplete
        }
    }
    
    public struct TodosView: View {
        let store: StoreOf<Todos>
    
        public init(store: StoreOf<Todos>) {
            self.store = store
        }
    
        public var body: some View {
            NavigationView {
                WithViewStore(self.store) { viewStore in
                    List {
                        ForEach(Array(zip(viewStore.todos.indices, viewStore.todos)), id: \.1.id) { index, todo in
                            HStack {
                                Button {
                                    viewStore.send(.todoCheckboxTapped(index: index))
                                } label: {
                                    Image(systemName: todo.isComplete ? "checkmark.square" : "square")
                                }
                                .buttonStyle(.plain)
                                TextField(
                                    "Untitled todo",
                                    text: viewStore.binding(
                                        get: { $0.todos[index].description },
                                        send: { .todoTextFieldChanged(index: index, text: $0) }
                                    )
                                )
                            }
                            .foregroundColor(todo.isComplete ? .gray : nil)
                        }
                    }
                    .navigationTitle("Todos")
                }
            }
        }
    }

    ```
    </details>

1. 新しいiOSのプロジェクトをライブラリの中に作成する
    1. ⇧⌘Nで新しいアプリを作成する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179037/ed897081-62ab-c65c-8323-39c3644cb563.png)
    1. Groupやファイル等の名前を適当に変更する
    1. Swiftファイルが配置されているフォルダの名前を変更した際にはDevelopment Assetsを変更する必要あり。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179037/5e90f83f-8465-3917-7644-d18387a984f1.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179037/d5a13cf7-3a7c-5dd6-a159-037910f7d168.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179037/e84565ba-7b3b-43c1-faa2-95f2ac606c8a.png)

1. File>Add Package>Local Packageで、TodosFeatureを選択する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179037/47a7f901-1b65-7712-1938-4261a2720997.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179037/b57c8737-1812-aff6-0282-1b528be3722b.png)
1. TodoFeatureをドラッグ&ドロップでPackagesの上に持ってくるそして、Packagesは削除する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179037/52a5e687-479d-a5ce-ed0d-a9a044e88bb8.png)
1. TARGETSにTodosFeatureを追加する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179037/267c027d-b5b8-6120-05a0-061a26d1fed3.png)
1. App.swiftを編集する
    ```swift:App.swift
    import ComposableArchitecture
    import SwiftUI
    import TodosFeature
    
    @main
    struct TodosApp: App {
        var body: some Scene {
            WindowGroup {
                TodosView(store: Store(initialState: Todos.State(todos: [
                    Todo(description: "Hand soup"),
                    Todo(description: "Milk"),
                    Todo(description: "iPhone")
                ]), reducer: Todos()))
            }
        }
    }
    ```
## 成果物
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179037/1d54ebed-c6fa-27e9-f013-0420a919de45.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179037/6ffc026d-bd2a-a5e3-d402-4697297e5689.png)

## 感想
なんとなくマルチモジュールをどんな感じで行うのか分かった気がする。(Package.swift命)\
isowordsではisowordsモジュールにAppフォルダがあるにも関わらず、表示されていない理由が分からないため知見のある方はぜひコメントをお願いいたします！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179037/d2db0d5f-8858-cbdb-7d28-343e7676a92e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179037/79d03ab2-2c16-9acf-bebe-bf4d9c885431.png)