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:
Users interact with a screenful of information at a time.
Users navigate from one screen to another. Each screen contains a different set of information, and is managed by a different view controller.
There are clearly-defined editing modes, and saving state is implicit.
Users often enter an editing mode by tapping an Edit button. In edit mode, they are frequently given the option to cancel the changes they’ve made. When the user taps Done, any changes they have made are considered to be committed—the user doesn’t have to select a Save menu item, for example.
Undo and redo messages are sent directly to the undo manager.
You are not provided with undo and redo methods that you can override.
Memory on devices is constrained.
You need to avoid consuming too much memory, otherwise your application may be terminated.
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; |
} |
} |
Copyright © 2011 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2011-06-03