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).
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];
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With