import SwiftUI import ComposableArchitecture struct ToDo : Identifiable, Equatable{ var id = UUID() var title: String var done: Bool } struct ToDoState : Equatable { var loading: Bool = false var error: String = "" var todos: [ToDo] = [] var completedToDos: [ToDo] { get{ return todos.filter { $0.done } } } var unCompletedToDos: [ToDo] { get{ return todos.filter { !$0.done } } } } enum ToDoAction: Equatable { case get case getResponse(Result<[ToDo], ApiError>) case append case toggle(UUID) case toggleResponse(Result) } struct ToDoEnvironment { var mainQueue: AnySchedulerOf var getAPI: () -> Effect<[ToDo], ApiError> var toggleAPI: (UUID) -> Effect } struct ApiError: Error, Equatable {} let toDoReducer = Reducer< ToDoState, ToDoAction, ToDoEnvironment> { state, action, environment in switch action { case .get: state.loading = true return environment.getAPI() .delay(for: 2, scheduler: environment.mainQueue.animation()) .catchToEffect() .map(ToDoAction.getResponse) case let .getResponse(.success(todos)): state.todos = todos state.loading = false return .none case let .getResponse(.failure(error)): state.error = error.localizedDescription state.loading = false return .none case .append: state.todos += [ToDo(title: "追加", done: false)] return .none case let .toggle(id): state.loading = true return environment.toggleAPI(id) .delay(for: 2, scheduler: environment.mainQueue.animation()) .catchToEffect() .map(ToDoAction.toggleResponse) case let .toggleResponse(.success(toDo)): state.todos.indices.forEach { if (state.todos[$0].id == toDo.id) { // 本来はAPIで返ってくる値をそのまま更新すればいいがAPI実装してない都合 state.todos[$0].done = !state.todos[$0].done } } state.loading = false return .none case let .toggleResponse(.failure(error)): state.error = error.localizedDescription state.loading = false return .none } } let initToDos = [ ToDo(title: "タスク1", done: false), ToDo(title: "タスク2", done: true), ] struct ContentView: View { var body: some View { VStack{ ToDoView( store: Store( initialState: ToDoState(), reducer: toDoReducer, environment: ToDoEnvironment( mainQueue: .main, getAPI: { Effect(value: initToDos) }, toggleAPI: { id in Effect(value: ToDo(id: id, title: "dummy", done: false)) } ) ) ) } } } struct ToDoView: View { let store: Store var body: some View { WithViewStore(self.store) { viewStore in ZStack { VStack{ if viewStore.error != "" { Text(viewStore.error) } Button(action: { viewStore.send(.append) }){ Text("追加") } Text("全ToDo") ToDoListView(todos: viewStore.todos, toggle: { id in viewStore.send(.toggle(id))}) Text("未完了ToDo") ToDoListView(todos: viewStore.unCompletedToDos, toggle: { id in viewStore.send(.toggle(id))}) Text("完了済みToDo") ToDoListView(todos: viewStore.completedToDos, toggle:{ id in viewStore.send(.toggle(id))}) }.onAppear { viewStore.send(.get) } if viewStore.loading { ProgressView() } } } } } struct ToDoListView: View { let todos: [ToDo] let toggle: (UUID) -> Void var body: some View { VStack{ List { ForEach(todos) { todo in ToDoRow(todo: todo, toggle: toggle) } } } } } struct ToDoRow: View { let todo: ToDo let toggle: (UUID) -> Void var body: some View { HStack { Text(todo.title) Spacer() Button(action: { toggle(todo.id) }){ if (todo.done) { Image(systemName: "checkmark.square.fill") .foregroundColor(.green) }else { Image(systemName: "square") } } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }