Using Undo on iPhone

UIKit supplements the behavior of the NSUndoManager class by establishing a framework for the distribution and selection of undo managers in an application.

Disabling Shaking as a Triggering Event

By default, users trigger an undo operation by shaking the device. If you don’t want this behavior, you need to tell the application to not respond to shake events as an edit request:

application.applicationSupportsShakeToEdit = NO;

You typically set this property when the application starts up, in the application delegate’s applicationDidFinishLaunching: or application:didFinishLaunchingWithOptions: methods.

Undo and the Responder Chain

An application can have one or more undo clients—objects that register and perform undo operations in their local contexts. Each of these objects has its own NSUndoManager object and the associated undo and redo stacks.

One example of this scenario involves custom views, each a client of an undo manager. For example, you could have a window with two custom views; each view can display text in changeable attributes (such as font, color, and size) and users can undo (or redo) each change to any attribute in either of the views. UIResponder helps you control the context of undo operations within the view hierarchy.

The UIResponder class declares the undoManager property through which objects that inherit from it (in particular views, and view controllers) can provide an undo manager to the framework. When the application receives an undo event, UIResponder goes up the responder chain (starting with the first responder) looking for a responder that returns an NSUndoManager object from undoManager. The first undo manager that is found is used for the undo or redo operation.

UIKit automatically creates a suitable alert panel for you based on the existence and state of the undo manger. If the user chooses to perform an undo or redo operation, the undo manager is sent an undo or redo message as appropriate.

You typically provide the undo manager in a view controller (see Design Patterns).If you want a view controller to provide an undo manager, the view controller must be willing to become first responder, and must become first responder when its view appears. Conversely, it should resign first responder when its view disappears. It does this by implementing the following code:

- (BOOL)canBecomeFirstResponder {
    return YES;
}
 
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self becomeFirstResponder];
}
 
- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    [self resignFirstResponder];
}

Design Patterns

In an iPhone application there are a number of patterns, conventions and constraints that come into play when considering undo support:

When supporting undo, you should heed these design patterns, conventions, and constraints.

It doesn’t necessarily make sense to support undo pervasively. The user’s expectation is that once an action has been taken, it is irreversible. Moreover, the context is important. Consider an application that displays a list of books, and allows you to navigate to a detail view that in turn allows you to edit individual properties of the book (such as its title, author, and copyright date). You might create a new book from the list screen, navigate between two other screens to edit its properties, then navigate back to the original list. It might seem peculiar if an undo operation in the list view undid a change to the author’s name that was made two screens away rather than deleting the entire book.

There’s also the issue of memory management. Each undo action requires that the data to perform the undo be kept in memory. In some applications, this may become a significant overhead. In general, it’s best if you try to keep memory footprint to a minimum. If your application supports editing modes, it may be appropriate to create an undo manager only when the user enters editing mode, and to dispose of the undo manager when the user leaves the mode.

Because undo and redo messages are sent directly to the undo manager, you should register as an observer of the undo manager’s change notifications. In the notification callbacks, you can propagate the change to the user interface as necessary.

Because the user may navigate between different screens during an editing operation, you may need to remind them what it is they’re undoing. It is useful to provide undo action titles so that it’s clear to the user what effect the undo or redo operation will have.

The following example illustrates how you might respond to change in editing state in a subclass of UITableView. If editing begins, you create an undo manager to track changes. You also register as an observer of undo manager change notifications, so that if an undo or redo operation is performed, the table view can be reloaded. When editing ends, de-register from the notification center and remove the undo manager.

- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
 
    [super setEditing:editing animated:animated];
 
    NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
 
    if (editing) {
        NSUndoManager *anUndoManager = [[NSUndoManager alloc] init];
        self.undoManager = anUndoManager;
        [undoManager setLevelsOfUndo:3];
        [anUndoManager release];
 
        [dnc addObserver:self selector:@selector(undoManagerDidUndo:)
                    name:NSUndoManagerDidUndoChangeNotification object:undoManager];
        [dnc addObserver:self selector:@selector(undoManagerDidRedo:)
                    name:NSUndoManagerDidRedoChangeNotification object:undoManager];
    }
    else {
        [dnc removeObserver:self];
        self.undoManager = nil;
    }
}