Replacing attributed text in UITextView with UndoManager support

Replacing attributed text in UITextView with UndoManager support

When working on apps with text editing capabilities, a must have feature is to allow users to undo their changes, UITextViews 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.