0

I have a UITextView with a formatting toolbar. After I press the "bold" button, it changes the text/selection and registers the operation with the text view's NSUndoManager.

When you try to undo, restoreText: is called over and over until you kill the app

Some sample code:

- (void)bold {
    NSRange range = self.textView.selectedRange;
    NSRange oldRange = range;
    NSString *oldText = self.textView.text;
    NSString *selection = [self.textView.text substringWithRange:range];
    self.textView.scrollEnabled = NO;
    self.textView.text = [self.textView.text stringByReplacingCharactersInRange:range
                                                           withString:[NSString stringWithFormat:@"<b>%@</b>", selection]];
    self.textView.scrollEnabled = YES;
    if (range.length == 0) {                // If nothing was selected
        range.location += 3; // Place selection between tags
    } else {
        range.location += range.length + 7; // Place selection after tag
        range.length = 0;
    }
    self.textView.selectedRange = range;
    [[self.textView.undoManager prepareWithInvocationTarget:self] restoreText:oldText withRange:oldRange];
    [self.textView.undoManager setActionName:@"bold"];
}

- (void)restoreText:(NSString *)text withRange:(NSRange)range {
    NSLog(@"restoreText:%@ %@",text, [NSNumber numberWithBool:[self.textView.undoManager isUndoing]]);
    NSString *oldText = self.textView.text;
    NSRange oldRange = self.textView.selectedRange;
    self.textView.scrollEnabled = NO;
    self.textView.text = text;
    self.textView.scrollEnabled = YES;
    self.textView.selectedRange = range;
    [[self.textView.undoManager prepareWithInvocationTarget:self] restoreText:oldText withRange:oldRange];
}

And the console:

2012-10-04 18:10:14.207 undotest[8861:c07] restoreText: 1
2012-10-04 18:10:14.633 undotest[8861:c07] restoreText:<b></b> 0
2012-10-04 18:10:15.117 undotest[8861:c07] restoreText: 0
2012-10-04 18:10:15.589 undotest[8861:c07] restoreText:<b></b> 0
2012-10-04 18:10:16.017 undotest[8861:c07] restoreText: 0
2012-10-04 18:10:16.557 undotest[8861:c07] restoreText:<b></b> 0

I've tried checking for [self.textView.undoManager isUndoing] before registering the invocation on restoreText:withRange:, but that crashes:

2012-10-04 18:10:49.297 undotest[8904:c07] restoreText: 1
2012-10-04 18:10:53.412 undotest[8904:c07] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '_endUndoGroupRemovingIfEmpty:: WebThreadSafeUndoManager 0x76551e0 is in invalid state, endUndoGrouping called with no matching begin
'

This code was working on iOS5 but stopped working on iOS6. I've filed a bug with Apple, but I'm not sure if I'm doing something wrong

Jorge Bernal
  • 265
  • 3
  • 11

2 Answers2

0

Same thing happens to my UITextView instance even for simple-undoing. This must be a bug, and it means that no undo feature can be implemented by yourself to your UITextView anymore. I wonder if anyone have successfully implemented undo features on iOS6 UITextView...

Alternatively, I desided to use replaceRange:withText: (TextInput Protocol) instead to implement my undo feature on UITextView. At least you can Undo replacing text with that method. You don't need to register undo action for that by yourself. That method does it automatically.

user1263865
  • 121
  • 1
  • 4
0

I had the same problem , and found that accessing the textView's text causes strange behaviour, like calling method repeatedly and crashes.

textView.text=@"" //this crashes

This can be solved by using replaceRange:withText: as suggested by user1263865, and if you want to customize the action to handle to undo/redo, you can register the action, but skip the part of modifying texts, since replaceRange:withText: already handles it.

For example if I want to make a clear text function which clear texts in textView as well as a custom message object, then restore it when user undo the action:

-(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.
}
b123400
  • 6,138
  • 4
  • 27
  • 27