iPhone OS 3.0 brought over the concept of NSUndoManager from the Mac, which is what enables undo on the iPhone. NSUndoManager maintains a stack of NSInvocations which are the opposite actions to any edits or other changes you make. For example,
- (void)observeValueForKeyPath:(NSString*)keyPath
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context
{
NSUndoManager *undo = [self undoManager];
// Grab the old value of the key
id oldValue = [change objectForKey:NSKeyValueChangeOldKey];
// Add edit item to the undo stack
[[undo prepareWithInvocationTarget:self] changeKeyPath:keyPath
ofObject:object
toValue:oldValue];
// Set the undo action name in the menu
[undo setActionName:@"Edit"];
}
can be used to observe changes in properties, creating inverse NSInvocations that will undo edits to those properties.
Core Data is not needed for undo, but it makes it much, much easier. It handles the creation of these undo actions for you every time you edit your data model, including complex actions like a cascading delete down a hierarchy of managed objects.
On the iPhone, to enable undo / redo, you need to set up a few things. First, NSManagedObjectContexts on the iPhone don't have an undo manager by default, so you need to create one:
NSUndoManager *contextUndoManager = [[NSUndoManager alloc] init];
[contextUndoManager setLevelsOfUndo:10];
[managedObjectContext setUndoManager:contextUndoManager];
[contextUndoManager release];
This code would typically go right after where you would have created your NSManagedObjectContext.
Once an undo manager is provided for your context, you need to enable the default gesture for undo on the iPhone, a shake of the device. To let your application handle this gesture automatically, place the following code within the -applicationDidFinishLaunching:
method in your application delegate:
application.applicationSupportsShakeToEdit = YES;
Finally, you will need to set up each view controller that will be capable of handling the shake gesture for undo. These view controllers will need to report back the undo manager to use for that controller by overriding the -undoManager
method:
- (NSUndoManager *)undoManager;
{
return [[[MyDatabaseController sharedDatabaseController] scratchpadContext] undoManager];
}
The view controllers will also need to be able to become the first responder to handle gestures, so the following method is needed:
- (BOOL)canBecomeFirstResponder
{
return YES;
}
The view controller will need to become the first responder when it appears onscreen. This can be done by calling [self becomeFirstResponder]
in -loadView
or -viewDidLoad
, but I have found that view controllers which appear onscreen immediately after launch need to have this message delayed a bit in order for it to work:
[self performSelector:@selector(becomeFirstResponder) withObject:nil afterDelay:0.3];
With all this in place, you should get automatic undo and redo support courtesy of Core Data, with a nice animated menu.