UndoManager On iOS
What is UndoManager?
UndoManager or NSUndoManager (in Objective-C) is one of the data structures that foundations packs. It is a stack that facilitates undo operations.
How does it work?
UndoManager deals with actions and not with states. The developer is responsible for maintaining the state using UndoManager. UndoManager by default groups all operations happening in one cycle. So, when you perform ‘undo’ those actions get reverted together.
Let us detail this with one example.
-
Say you perform a draw and a cut in one run loop cycle (it is probably impossible for user actions but let us consider the case).
-
These actions get grouped together, so when you ‘undo’ the draw and the cut are reverted together.
Can I see an example?
import Foundation
enum Type {
case book
case pen
case pencil
}
class Store: CustomStringConvertible {
weak var undoManager: UndoManager?
var inventory: [Type: Int] = [
.book: 24,
.pen: 8,
.pencil: 10
]
var description: String {
"""
----------------------------
Book : \(inventory[.book] ?? 0)
Pen : \(inventory[.pen] ?? 0)
Pencil: \(inventory[.pencil] ?? 0)
----------------------------
"""
}
init(undoManager: UndoManager) {
self.undoManager = undoManager
}
func remove(_ type: Type, count: Int) {
store.inventory[type] = max(0, store.inventory[type]! - count)
undoManager?.beginUndoGrouping()
undoManager?.registerUndo(withTarget: self) { [weak self] _ in
guard let self = self else { return }
self.add(type, count: count)
}
undoManager?.endUndoGrouping()
}
func add(_ type: Type, count: Int) {
store.inventory[type]! += count
undoManager?.beginUndoGrouping()
undoManager?.registerUndo(withTarget: self) { [weak self] _ in
guard let self = self else { return }
self.remove(type, count: count)
}
undoManager?.endUndoGrouping()
}
}
let undoManager = UndoManager()
// let us opt manual grouping since everything will run in single cycle
undoManager.groupsByEvent = false
let store = Store(undoManager: undoManager)
print(store)
store.add(.book, count: 3)
print(store)
store.remove(.pen, count: 2)
print(store)
undoManager.undo()
print(store)
undoManager.undo()
print(store)
undoManager.redo()
print(store)
Notes
While using UndoManager be careful around creating retain cycles.
References
- Registering Undo with Target
- Introduction to Undo Architecture