5

I want to change some or all of the attributed text of a rich UITextView (iOS 6), and allow the user to undo the change.

After reading NSUndoManager documentation, I tried the first way:

“Simple undo” based on a simple selector with a single object argument.

I expected an undo operation to be as simple as:
Declare this method:

- (void)setAttributedStringToTextView:(NSAttributedString *)newAttributedString {

     NSAttributedString *currentAttributedString = self.textView.attributedText;

    if (! [currentAttributedString isEqualToAttributedString:newAttributedString]) {
         [self.textView.undoManager registerUndoWithTarget:self
                                    selector:@selector(setAttributedStringToTextView:)
                                      object:currentAttributedString];
         [self.textView.undoManager setActionName:@"Attributed string change"];
         [self.textView setAttributedText:newAttributedString];
    }
}

Change the text in my UITextView by calling:

[self setAttributedStringToTextView:mutableAttributedString];

But after doing that, NSUndoManager says it cannot undo.

NSLog(@"Can undo: %d", [self.textView.undoManager canUndo]);
// Prints: "Can undo: 0"




So I tried the second way:

“Invocation-based undo” which uses an NSInvocation object.

Declare this:

- (void)setMyTextViewAttributedString:(NSAttributedString *)newAttributedString {

        NSAttributedString *currentAttributedString = [self.textView attributedText];
    if (! [currentAttributedString isEqualToAttributedString:newAttributedString]) {
        [[self.textView.undoManager prepareWithInvocationTarget:self]
         setMyTextViewAttributedString:currentAttributedString];
        [self.textView.undoManager setActionName:@"Attributed string change"];
        [self.textView setAttributedText:newAttributedString];
    }
}

and change the text with:

[self setMyTextViewAttributedString:mutableAttributedString];

After that, NSUndoManager also says it cannot undo.

Why?

Note that the user is editing the UITextView when triggering the code that will change the attributed text.




A workaround would be to replace the text directly via UITextInput protocol method. The following method is quite convenient, but I haven't found an equivalent for NSAttributedString. Did I miss it?

- (void)replaceRange:(UITextRange *)range withText:(NSString *)text

An hack suggested here is to simulate a paste operation. If possible, I would prefer to avoid this (no reason yet, just feels too dirty to not come back bite me later).

Guillaume
  • 21,685
  • 6
  • 63
  • 95
  • Have you verified that `self.textView`, `self.textView.attributedText`, and `self.textView.undoManager` are all non-`nil`? – Peter Hosey Sep 26 '12 at 09:07
  • Yes. self.textView.attributedText = newValue is actually updated in the user interface. And textView.undoManager is member of class WebThreadSafeUndoManager, and is the same instance for all the life cycle of my viewController. – Guillaume Sep 26 '12 at 09:12
  • 1
    I noticed the same problem. I can undo some operations but not an attributedString replacement. Found no workaround (see http://stackoverflow.com/questions/12501541/uitextview-undo-manager-do-not-work-with-replacement-attributed-string-ios-6) – Denis Sep 27 '12 at 11:04

1 Answers1

2

I'm kinda still in shock this works. I posted the same answer over here UITextView undo manager do not work with replacement attributed string (iOS 6).

- (void)applyAttributesToSelection:(NSDictionary*)attributes {
    UITextView *textView = self.contentCell.textView;

    NSRange selectedRange = textView.selectedRange;
    UITextRange *selectedTextRange = textView.selectedTextRange;
    NSAttributedString *selectedText = [textView.textStorage attributedSubstringFromRange:selectedRange];

    [textView.undoManager beginUndoGrouping];
    [textView replaceRange:selectedTextRange withText:selectedText.string];
    [textView.textStorage addAttributes:attributes range:selectedRange];
    [textView.undoManager endUndoGrouping];

    [textView setTypingAttributes:attributes];
}
Community
  • 1
  • 1
Brad G
  • 2,528
  • 1
  • 22
  • 23