【Swift】UndoManagerとは?概要や活用シーン、注意点までわかりやすく解説!

B!

「さっきの操作を取り消したい」「やっぱり元に戻したい」。

SwiftUIアプリでこの体験を実現する中核が UndoManager です。

SwiftUIでは環境値 @Environment(\.undoManager) から取得でき、さらに SwiftData と組み合わせると、データ編集の取り消し/やり直しも自動連携させやすくなります。

システム標準の三本指スワイプやシェイク操作とも親和性が高く、自然なUndo/Redo体験を提供できます。

UndoManagerとは?

UndoManagerFoundationが提供するクラス(オブジェクト) で、ユーザー操作の“逆処理”を積み上げておき、後から「取り消す(Undo)」「やり直す(Redo)」を実行できるようにする仕組みです。

SwiftUI では @Environment(\.undoManager) から参照でき、registerUndo で逆処理を登録、undo() / redo() で実行します。

SwiftData を使っている場合は、データ変更をUndoスタックに自動登録する仕組みが用意されており、最小のコードで取り消し体験を組み込めます。

実装時に覚えておきたい

UndoManager の実装時にまず覚えておきたいプロパティ/メソッドを用途別にまとめます。

逆操作の登録(最重要)

  • registerUndo(withTarget:handler:)
    取り消し時に実行する処理を登録します。ターゲットは classのインスタンス である必要があります。Undo内で「元の操作」を再登録すると Redo が成立します。

  • setActionName(_:)
    「元に戻す ◯◯」の 表示名 を設定します(メニューやアクセシビリティ文言に反映されます)。

実行(Undo/Redo)

  • undo() / redo()
    直近の 取り消し/やり直し を実行します。

  • undoNestedGroup()
    ネストしたグループごとに 一段だけ 取り消します(高度なケース向け)。

グループ化(複数の変更を1回で戻す)

  • beginUndoGrouping() / endUndoGrouping()
    登録した複数の操作を ひとかたまり(グループ) にします。defer { endUndoGrouping() }閉じ忘れ防止 を。

  • groupingLevel(読み取り)
    現在の 入れ子レベル を確認します。

状態の問い合わせ

  • canUndo / canRedo(Bool)
    いま 取り消せる/やり直せる かを返します(ボタンの disabled 制御などに)。

  • isUndoing / isRedoing(Bool)
    現在 Undo/Redoを実行中か を示します(再入防止に便利)。

  • isUndoRegistrationEnabled(Bool)
    登録を一時停止/再開 しているかを示します(大量更新の間だけ止めたいときに)。

履歴サイズとクリア

  • levelsOfUndo(Int)
    履歴の最大段数 を設定します。メモリや処理時間に合わせて上限を決めます。

  • removeAllActions() / removeAllActions(withTarget:)
    すべて(または特定ターゲット)の Undo履歴を破棄 します。

表示名の取得(必要に応じて)

  • undoActionName / redoActionName(String)
    設定済みの アクション名 を返します。

  • undoMenuItemTitle / redoMenuItemTitle(String)
    メニューなどに使える 完全な文言(例:「元に戻す 色の変更」)を返します。

使い方

次にUndoManager の使い方を説明します。

基本は次の流れです。

  1. ユーザー操作を実行する前に、逆操作を registerUndo で登録します。
  2. 取り消し時に呼ばれる逆処理の中で、元の操作を再登録すると、Redoまで一貫して成立します。
  3. SwiftUIなら @Environment(\.undoManager) を使い、必要に応じて SwiftDataの ModelContext.undoManager に割り当てます。(Apple Developer)

補足:SwiftDataの modelContainer は通常、環境の undoManager を利用します。多くの場合は追加設定なしで、システム標準のUndoジェスチャ(三本指スワイプ、シェイク)が効きます。

具体例

1. SwiftUIの状態変更にUndoを付与(ViewModel方式)

SwiftUIの Viewstruct(値型)なので、画面の更新のたびに使い捨てで作り直される前提です。

一方、Undo の呼び出し先はずっと同じ相手(同じインスタンス)である必要があります。

そこで、状態と処理を class(参照型)の ViewModel にまとめ、その同じインスタンスregisterUndo のターゲットにします。こうすると、Undo/Redoがいつでも同じ相手に確実に届くようになります。

ポイント

  • View(値型)は再生成されやすく、Undoの“呼び戻し先”としては不安定。
  • ViewModel(参照型)は同一インスタンスを保てるので、registerUndo(withTarget:)ターゲットに最適
  • 実装は「@Environment(\.undoManager) を受け取る → ViewModelに注入 → ViewModel内で registerUndo」の流れにするとスッキリします。

2. SwiftData:編集の取り消しを自動連携

SwiftDataは、データ変更をUndoに自動記録できます。

明示的に ModelContext.undoManager に環境の UndoManager を割り当てれば、ボタンやジェスチャで直前の変更を取り消せます。

ポイント

  • ModelContextundoManager をセットすると、挿入・更新・削除がUndo対象になります。
  • modelContainer を使う通常構成では、システム標準ジェスチャ(三本指スワイプ/シェイク)でUndo/Redoできます。

3. 複数の変更を1回のUndoにまとめる(グループ化)

関連する変更をひとかたまりにすると、ユーザーが戻しやすくなります。

ポイント

  • beginUndoGrouping() は「まとめ箱を開ける」、endUndoGrouping() は「箱を閉じる」です。
    処理の途中で早期 return しても必ず閉じるように、defer { endUndoGrouping() } と書いておくと安全です。

  • ひとつの箱(グループ)に入れた変更は、ユーザーからは**「元に戻す」を1回実行するだけで全部まとめて**巻き戻せます。
    例:名前変更+アイコン変更+自己紹介変更を同じグループに入れておけば、1回のUndoで3つとも元に戻ります。

    注意点

    UndoManager は便利な一方で、実装の落とし穴もあります。

    下記ポイントを押さえた上で活用しましょう。

    逆操作の完全性

    取り消すための逆操作を必ず登録します。

    さらに、Undo処理の中で元の操作を再登録しておくと、Redoまで自然に成立します。

    例:undo.registerUndo(withTarget:) のクロージャ内で、元のメソッドをもう一度呼び出します。

    参照サイクル

    registerUndo のクロージャで self を強参照すると解放されにくくなります。

    [weak self] を使うか、処理を class ベースの ViewModel/Proxy にまとめてターゲットにします。

    グループ化の境界

    beginUndoGrouping() で“箱を開け”、最後に endUndoGrouping() で“箱を閉じる”対応を必ず取ります。

    早期リターンでも閉じ忘れないよう、defer { endUndoGrouping() } で管理すると安全です。

    履歴サイズ

    細かく記録しすぎるとメモリや処理時間に影響します。

    操作の粒度を見直し、必要なら levelsOfUndo で上限を設定します。

    環境依存

    undoManager は状況によって nil の場合があります。

    guard let undo = undoManager else { … } のようにオプショナルとして安全に扱い、関連ボタンも無効化するなどUI側の配慮を行います。

    SwiftData連携の前提

    データ編集をUndo対象にするには、ModelContext.undoManager明示的に紐づけます。

    表示タイミングなどで一度だけ context.undoManager = undoManager を設定しておきます。

    まとめ

    UndoManager は、ユーザーが安心して試せる余地をアプリに与える重要な基盤です。

    SwiftUIでは @Environment(\.undoManager) を通じて簡単に扱え、SwiftDataと組み合わせればデータ編集のUndo/Redoも自動連携しやすくなります。

    逆操作の登録→Undo中の再登録でRedo成立→必要に応じてグループ化、という基本を押さえ、自然で信頼できる編集体験を設計しましょう。

    参考リンク

    最新の記事はこちらから