When working on apps with text editing capabilities, a must have feature is to allow users to undo their changes, UITextView
s makes this easier via the undoManager property. Text changes performed with the replaceRange:withText: are automatically registered in the undo manager and rolling them back is done by simply calling textView.undoManager?.undo()
. Unfortunately, there is no equivalent method that we can use when replacing attributed text. In this case we need to manually handle the registration of undo handlers. This is how I do it for Lines
private func replaceRange(_ range: NSRange, withAttributedText text: NSAttributedString) {
let previousText = attributedText.attributedSubstring(from: range)
let previousSelectedRange = selectedRange
undoManager?.registerUndo(withTarget: self, handler: { target in
target.replaceRange(NSMakeRange(range.location, text.length),
withAttributedText: previousText)
})
textStorage.replaceCharacters(in: range, with: text)
selectedRange = NSMakeRange(previousSelectedRange.location, text.length)
}
The important thing is to model the function in a way that we can call it recursively. This way we register an undo handler when the we call the function directly and also when the function is called by the undo manager which allows us to redo
.