NSUndoManager and UITextView on iOS6

UITextView and NSUndoManager don’t work really well on iOS6, directly modifying textView’s text causes crash.

For example, the following code doesn’t work as expected.

-(void) clearEditText {
    //Register undo
    [[textView.undoManager prepareWithInvocationTarget:self] undoClearEditText:[textView.text retain] withMessage:[self.message retain]];
    //message is retained because undo manager will not retain, it will be released when user undo.

    // Clear text field
    textView.text=@"";

    self.message = /*new message*/;
}
-(void) undoClearEditText:(NSString*)restoreText withMessage:(Message*)restoreMessage {
    //Register redo
    [[textView.undoManager prepareWithInvocationTarget:self] undoClearEditText:[textView.text retain] withMessage:[self.message retain]];

    self.message = restoreMessage;
    textView.text=restoreText;

    [restoreMessage release];
    [restoreText release];
    //message is released because it is retained when it is added to undo manager.
}

After trial and error, it seems that text changes are handled by undoManager and I shouldn’t access it directly.

Instead UITextView’s replaceRange:withText: should be used, it handles undo and redo automatically, so no extra work is needed if you only want to replace text.

For customized undo/redo action, it can be done like this:

I am setting a new message object when user clears texts, and put that object back when the user undo the clear action, redo works as well.

-(void) clearEditText {
    //Register undo
    [[textView.undoManager prepareWithInvocationTarget:self] undoClearEditTextWithMessage:[self.message retain]];
    //message is retained because undo manager will not retain, it will be released when user undo.

    // Clear text field
    UITextPosition *beginning = textView.beginningOfDocument;
    UITextPosition *end= textView.endOfDocument;
    UITextRange *textRange = [textView textRangeFromPosition:beginning toPosition:end];
    [textView replaceRange:textRange withText:@""];

    self.message = /*new message*/;
}
-(void) undoClearEditTextWithMessage:(Message*)restoreMessage {
    //Register redo
    [[textView.undoManager prepareWithInvocationTarget:self] undoClearEditTextWithMessage:[self.message retain]];

    self.message = restoreMessage;

    [restoreMessage release];
    //message is released because it is retained when it is added to undo manager.
}

Text changes don’t need to be handled, replaceRange:withText: will do it automatically.