Change Tracking and Undo Operations

The saveless-model feature of the UIDocument class ensures that document data is automatically saved at frequent intervals, relieving users of the need to explicitly save their documents. UIDocument implements much of the behavior for the saveless model, but a document-based application must play its own part to make the feature work.

How UIKit Saves Document Data Automatically

The saveless model implemented by the UIKit framework for documents has two main parts: a mechanism for marking a document as needing to be saved and a variable period for when the framework checks that flag. Periodically, UIKit calls the hasUnsavedChanges method of a UIDocument object and evaluates the returned value. If the value is YES, it saves the document data to the document file. The period between checks of the hasUnsavedChanges value varies according to several factors, including the rate of input by the user.

A document-based application sets the value returned by hasUnsavedChanges indirectly, either by implementing undo and redo or by tracking changes to the document. Change tracking requires the application to call the updateChangeCount: method, passing in UIDocumentChangeDone (a constant of type UIDocumentChangeKind). When an application registers an undo action and then sends undo or redo messages to the document’s undo manager, UIDocument calls updateChangeCount: on its behalf.

Because giving users the ability to undo and redo changes can be a differentiating feature, that approach is recommended for most applications.

Implementing Undo and Redo

You can implement undo and redo operations in your application by following the procedures and recommendations in Undo Architecture. Note that UIDocument defines an undoManager property. You can get the default NSUndoManager object by accessing this property, or you can assign your own NSUndoManager object to it. The undo manager must be associated with the UIDocument object through the property in order to enable change tracking and thus automatic saving of document data.

Listing 5-1 illustrates an implementation of undo and redo for a text field.

Listing 5-1  Implementing undo and redo for a text field

- (void)textFieldDidEndEditing:(UITextField *)textField {
    self.undoButton.enabled = YES;
    self.redoButton.enabled = YES;
 
    if (textField.tag == 1) {
        [self setLocationText:textField.text];
    }
    // code for other text fields here....
}
 
- (void)setLocationText:(NSString *)newText {
    NSString *currentText = _document.location;
    if (newText != currentText) {
        [_document.undoManager registerUndoWithTarget:self
            selector:@selector(setLocationText:)
            object:currentText];
        _document.location = newText;
        self.locationField.text = newText;
    }
}
 
- (IBAction)handleUndo:(id)sender {
    [_document.undoManager undo];
    if (![_document.undoManager canUndo]) self.undoButton.enabled = NO;
}
 
- (IBAction)handleRedo:(id)sender {
    [_document.undoManager redo];
    if (![_document.undoManager canRedo]) self.redoButton.enabled = NO;
}

Implementing Change Tracking

To implement change tracking instead of implementing undo/redo, call the updateChangeCount: method on the UIDocument object at the appropriate points in your code. Just as when you register an undo action, it’s typically at the point where you update the document’s model object with data the user has just entered. The parameter passed in should be a UIDocumentChangeDone constant.

Listing 5-2 shows how you might call updateChangeCount: from within a UITextViewDelegate method that is called when a change is made in a text view.

Listing 5-2  Updating the change count of a document

-(void)textViewDidChange:(UITextView *)textView {
    _document.documentText = textView.text;
    [_document updateChangeCount:UIDocumentChangeDone];
}