NSUndoManager provides an awesomely simple method of undoing, that handles groups, menus and unlimited undo/redo. prepareWithInvocationTarget converts the undo manager into a proxy for the target, which records the next method call as an undo step. The method you call on undo manager should then be the step to undo what is currently being done. This is all you need do! It means that setter methods with undo can be as simple as this:
- (void)setSize:(float)newSize { [[undoManager prepareWithInvocationTarget:self] setSize:size]; //call to undo current set size = newSize; }
Live user interfaces are much more usable. An example is a slider that updates the size of an object as you drag. Unfortunately live updating the object like this involves setting the size for every twitch of the mouse. This quickly stacks up hundreds of undo actions. Not only do these take up lots of memory, they cause NSUndoManager to slow down and your whole app takes a hit. One solution is to use a undo queue as suggested by Mark Dalrymple Undology 101. But this requires a restructuring of code that is both tedious and obsfucating.
My solution to this problem is to provide a simple sub-class of NSUndoManager that handles this case without any change to your code structure. I introduce two new methods prepareWithSkippableInvocationTarget and registerSkippableUndoWithTarget that work in the same way as prepareWithInvocationTarget and registerUndoWithTarget except that they skip invocations that are duplicates of the previous undo call. In this way none of the existing functionality of NSUndoManager is changed, and it is extended to provide duplicate removal for skippable methods.
- (void)setSize:(float)newSize { [[undoManager prepareWithSkippableInvocationTarget:self] setSize:size]; size = newSize; }
That's it!
The source code: TDUndoManager.zip
A test app and code: TDUndoManagerTest.zip (69kB)