SwiftUI＋TCAで Undo / Redo 機能を作る方法

# はじめに
TCAでUndo / Redoを実装する方法を紹介します。

# 作るもの
Undo / Redo機能を持つカウンターアプリを作ります。

# デモ
https://twitter.com/shota_appdev/status/1671158013556846595?s=61&t=RbpUNobBW7OiUJOHqHWs8w

# 準備
Swift Package ManagerでTCAをインストールします。

https://github.com/pointfreeco/swift-composable-architecture

# コード全体
```swift
import SwiftUI
import ComposableArchitecture

struct CounterFeature: ReducerProtocol {
    // #1
    struct State: Equatable {
        var count: Int
        var undoStack: [Action] = []
        var redoStack: [Action] = []
    }
    
    // #2
    enum Action: Equatable {
        case setCount(Int)
        case increment
        case decrement
        case increment2
        case decrement2
        case undo
        case redo
    }
    
    var body: some ReducerProtocol<State, Action> {
        Reduce { state, action in
            switch action {
            case let .setCount(count):
                state.count = count
            case .increment:
                // #3
                state.undoStack.append(.setCount(state.count))
                state.redoStack = []
                state.count += 1
            case .decrement:
                state.undoStack.append(.setCount(state.count))
                state.redoStack = []
                state.count -= 1
            case .increment2:
                state.undoStack.append(.setCount(state.count))
                state.redoStack = []
                state.count += 2
            case .decrement2:
                state.undoStack.append(.setCount(state.count))
                state.redoStack = []
                state.count -= 2
            case .undo:
                // #4
                guard let last = state.undoStack.popLast() else { return .none }
                state.redoStack.append(.setCount(state.count))
                return .task { last }
            case .redo:
                // #5
                guard let last = state.redoStack.popLast() else { return .none }
                state.undoStack.append(.setCount(state.count))
                return .task { last }
            }
            return .none
        }
    }
}

struct CounterView: View {
    let store: StoreOf<CounterFeature>
    
    var body: some View {
        WithViewStore(
            store,
            observe: { $0 }
        ) { viewStore in
            NavigationStack {
                VStack {
                    Text("\(viewStore.count)")
                        .font(.title)
                    HStack(spacing: 16) {
                        Button("-2") {
                            viewStore.send(.decrement2)
                        }
                        Button("-1") {
                            viewStore.send(.decrement)
                        }
                        Button("+1") {
                            viewStore.send(.increment)
                        }
                        Button("+2") {
                            viewStore.send(.increment2)
                        }
                    }
                    .controlSize(.large)
                    .buttonStyle(.bordered)
                }
                .toolbar {
                    Button {
                        viewStore.send(.undo)
                    } label: {
                        Image(systemName: "arrow.uturn.backward")
                    }
                    .disabled(viewStore.undoStack.isEmpty)

                    Button {
                        viewStore.send(.redo)
                    } label: {
                        Image(systemName: "arrow.uturn.forward")
                    }
                    .disabled(viewStore.redoStack.isEmpty)
                }
            }
        }
    }
}
```

# 解説
## #1
Stateを定義します。
undoStackとredoStackはActionの配列です。

## #2
Actionを定義します。
undoStackは、undo時に使うActionを保存しておくためのものです。
redoStackは、redo時に使うActionを保存しておくためのものです。

## #3
カウントアップ/カウントダウンするそれぞれのActionは、処理を実行する前の状態をセットするActionをundoStackに追加します。

## #4
undo時にundoStackからActionを取り出して、実行します。
redoStackには、undoする前の状態をセットするActionを追加します。

## #5
redo時にはredoStackからActionを取り出して、実行します。
undoStackには、redoする前の状態をセットするActionを追加します。

# おわりに
GitHubにもソースコードをあげておきました。
まだまだ改善の余地はありますが、参考になれば幸いです。

https://github.com/Shota-Abe/tca-undo-redo-example