Implementing Undo/Redo Feature in UITextView with IME Support

Currently, we are implementing an undo/redo feature in UITextView.

However, we cannot use the built-in UndoManager in UITextView because we have multiple UITextView instances inside a UICollectionView.

Since UICollectionView recycles UITextView instances, the same UITextView might be reused in different rows, making the built-in UndoManager unreliable.

The shouldChangeTextIn method in UITextViewDelegate is key to implementing undo/redo functionality properly. Here is an example of our implementation:

extension ChecklistCell: UITextViewDelegate {
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        // Get the current text
        let s = textView.text ?? ""
        
        // Get the starting position of the change
        let start = range.location
        
        // Get the number of characters that will be replaced
        let count = range.length
        
        // Get the number of characters that will be added
        let after = text.count
        
        print(">>>> The current text = \"\(s)\"")
        print(">>>> The starting position of the change = \(start)")
        print(">>>> The number of characters that will be replaced = \(count)")
        print(">>>> The number of characters that will be added = \(after)")
        print(">>>>")
        
        if let delegate = delegate, let checklistId = checklistId, let index = delegate.checklistIdToIndex(checklistId) {
            delegate.attachTextAction(s: s, start: start, count: count, after: after, index: index)
        }
        
        return true
    }
}

Working scene behind the UITextViewDelegate


However, this implementation does not work well with non-English input using an IME. When using an IME, there is an intermediate input before the final input is produced. For example, typing "wo" (intermediate input) produces "我" (final input). Currently, UITextViewDelegate captures both "wo" and "我".

UITextViewDelegate captures both "wo" and "我"

Is there a way to ignore the intermediate input from IME and only consider the final input?


In Android, we use the beforeTextChanged method in TextWatcher to seamlessly ignore the intermediate input from IME and only consider the final input. You can see this in action in this

Android captures only "我"

Is there an equivalent way in iOS to ignore the intermediate input from IME and only take the final input into consideration?

Implementing Undo/Redo Feature in UITextView with IME Support
 
 
Q