私の個人アプリを作る流れを全部見せます その10:UndoとRedo

本企画の全記事はこちらから

今回はUndoとRedoの機能をつけます。

UndoManager

残念ながら「Swift 4プログラミング入門」にはUndoに関する説明はありません。

そこでAppleの公式ドキュメントを見てみると以下のページが参考になりそうでした。

https://developer.apple.com/documentation/foundation/undomanager/2427208-registerundo

このregisterUndo(withTarget:handler:)を使えば実装できそうです。

今回は以下のようにsetColorというメソッドを用意し、今の色に戻す操作をUndoManagerに登録することで実現しました。

EditorView.swift
let dotsUndoManager = UndoManager()

// 該当インデックスの色を変更するメソッド
func setColor(_ color: UIColor, atIndex index: Int) {
    guard index < dots.count else { return }
    let prevColor = dots[index]
    // 色が現在と違う場合更新し、Undoできるようにする
    if color != prevColor {
        dots[index] = color
        dotsUndoManager.registerUndo(withTarget: self) { $0.setColor(prevColor, atIndex: index) }
    }
}

あとはツールバーのボタンタップ時にundo()redo()を呼び出してあげるだけでOKです。 ボタンとメソッドをつなぐ手順は「Chapter 12 主なUIパーツ」など様々な箇所で紹介しています。

canUndo

UndoやRedoできない状態のときにボタンが押せるのは不親切です。 ボタンの状態切り替えなどに使えるように、UndoManagerにはcanUndo、canRedoというプロパティが用意されています。

以下のようにdotsDidChangeが呼ばれたタイミングで各ボタンの状態を更新するようにしました。 この書き方は「Section 3.8 クロージャー」で説明しています。

EditorView.swift
var dotsDidChange: () -> Void = {}

func addDot(_ touches: Set<UITouch>) {
    // `i`を求める処理を省略

    setColor(color, atIndex: i)

    setNeedsDisplay()
    dotsDidChange()
}

func undo() {
    dotsUndoManager.undo()
    setNeedsDisplay()
    dotsDidChange()
}

func redo() {
    dotsUndoManager.redo()
    setNeedsDisplay()
    dotsDidChange()
}
EditViewController.swift
editorView.dotsDidChange = {
    self.undoItem.isEnabled = self.editorView.canUndo
    self.redoItem.isEnabled = self.editorView.canRedo
}

これで操作が戻せるようになりました 早速試してみます。

はみ出したのでUndoします。
(この状態ではまだRedoボタンが無効です)

今度は戻しすぎたのでRedo…。

無事、円が描けました。

1ドットずつ戻す仕様なので改善の余地ありですが、そこはリリース後の課題として、このまま進めたいと思います。

この状態のプロジェクトのダウンロードはこちらから。

https://github.com/tnantoka/PixelArtPocket/releases/tag/my-app-dev-flow-10