2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-06-20

はじめに

TCAでUndo / Redoを実装する方法を紹介します。

作るもの

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

デモ

準備

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

コード全体

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にもソースコードをあげておきました。
まだまだ改善の余地はありますが、参考になれば幸いです。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?