Reputation: 795
iOS 6 has been updated to use UITextView for rich text editing (a UITextView now earns an attributedText property —which is stupidly non mutable—). Here is a question asked on iOS 6 Apple forum under NDA, that can be made public since iOS 6 is now public...
In a UITextView, I can undo any font change but cannot undo a replacement in a copy of the view's attributed string. When using this code...
- (void) replace: (NSAttributedString*) old with: (NSAttributedString*) new
{
1. [[myView.undoManager prepareWithInvocationTarget:self] replace:new with:old];
2. old=new;
}
... undoing is working well.
But if I add a line to get the result visible in my view, the undoManager do not fire the "replace:with:" method as it should...
- (void) replace: (NSAttributedString*) old with: (NSAttributedString*) new
{
1. [[myView.undoManager prepareWithInvocationTarget:self] replace:new with:old];
2. old=new;
3. myView.attributedText=[[NSAttributedString alloc] initWithAttributedString:old];
}
Any idea? I have the same problem with any of the replacement methods, using a range or not, for MutableAttributedString I tried to use on line "2"...
Upvotes: 5
Views: 2272
Reputation: 3773
UITextView
has undoManager
that will manage undo and redo for free without requiring any additional code.
Replacing its attributedText
will reset the undoManager
(Updating text and its attributes in textStorage
not work for me either). However, I discovered that undo and redo functionality works normally when formatting text without replacing attributedText
but by standard edit actions (Right click on highlighting text > Font > Bold (Mac Catalyst)).
To ensure that undoManager
works properly, you need to use only certain specific methods of UITextView
. Using other methods may break the undo functionality of UITextView
.
Editing Text
allowsEditingTextAttributes
of UITextView
to be true
, this will make UITextView
support undo and redo of attributedText
.self.textView.allowsEditingTextAttributes = true
attributedText
, use replace(_:withText:)
of UITextInput
, or insertText(_:)
and deleteBackward()
of UIKeyInput
that UITextView
conforming to.self.textView.replace(self.textView.selectedTextRange!, withText: "test")
Updating Attributes
If you want to change attributes of text, use updateTextAttributes(conversionHandler:)
of UITextView
instead.
self.textView.updateTextAttributes { _ in
let font = UIFont.boldSystemFont(ofSize: 17)
let attributes: [NSAttributedString.Key: Any] = [
.font: font,
]
return attributes
}
or
self.textView.updateTextAttributes { attributes in
let newAttributes = attributes
let font = UIFont.boldSystemFont(ofSize: 17)
let newAttributes: [.font] = font
return newAttributes
}
Inserting Attachments
According to the documentation for init(attachment:)
in NSAttributedString
.
This is a convenience method for creating an attributed string containing an attachment using
character
(NSTextAttachment.character) as the base character.
If you want to add an attachment using updateTextAttributes
, you should insert a special character (The attachment will not show up if you are not using this character.) for the attachment first (NSTextAttachment.character
).
For example,
let attachment = NSTextAttachment(image: image)
let specialChar = String(Character(UnicodeScalar(NSTextAttachment.character)!))
textView.insertText(specialChar)
textView.selectedRange = NSRange(location: textView.selectedRange.lowerBound-specialChar.count, length: specialChar.count)
textView.updateTextAttributes { attributes in
var newAttributes = attributes
newAttributes[.attachment] = attachment
return newAttributes
}
For changing text and its attributes in specific range, modify the selectedRange
or selectedTextRange
of UITextView
.
To implement undo and redo buttons, check this answer : https://stackoverflow.com/a/50530040/8637708
I have tested with Mac Catalyst, it should work on iOS and iPadOS too.
Upvotes: 2
Reputation: 2593
Umm, wow I really didn't expect this to work! I couldn't find a solution so I just started trying anything and everything...
- (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];
}
Upvotes: 4
Reputation: 1
The Undo Manager is reset after setting its 'text' or 'attributedText' property, this is why it does not work. Whether this behavior is a bug or by design I don't know.
However, you can use the UITextInput protocol method instead.
This works.
Upvotes: 0