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
- You need to set the
allowsEditingTextAttributes of UITextView to be true, this will make UITextView support undo and redo of attributedText.
self.textView.allowsEditingTextAttributes = true
- If you want to change the text of
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.