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
}
}
This is how it looks like.
https://www.facebook.com/wenotecolor/videos/2174891132886868 – Undo/ redo works pretty well in English
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?
2
Answers
Since the IME is registering each character separately, I would recommend simply discerning between English and Mandarin characters and only registering the latter. The Mandarin checking aspect of was inspired by a SO article about checking Mandarin characters in Swift
For the English version of the app, this could easy be disabled.
Here are steps how I did it for a client few years back,
First you need to create properties to store the
undo
andredo
managers:Next, create an instance of
CustomTextView
and set it up:Finally, add
undo
andredo
buttons to trigger the corresponding actions:That’s how you do it