I have a UITextView in a ViewController. I've implemented shouldChangeTextInRange delegate method from UITextViewDelegate. The range value I obtain through the above delegate method for deleting a single character is something like {location, 1}.
But if I use the slide-to-type mechanism to insert a word before performing delete operation, the entire word gets deleted instead of a single character. I need to keep track of this.
For example, I insert "Hello" using the slide-to-type mechanism. If I press delete key in the keyboard, I get {4, 1} as range but the entire word "Hello" has been deleted in text view. In this case, I need the range to be {0, 5} much like selecting the text "Hello" and deleting it.
Is there a way to differentiate between normal delete operation and slide-to-type delete operation? How can I get the actual range that has been deleted in the text view?
A little help is very much appreciated.
This is what I've tried.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.addTextView()
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
}
extension ViewController {
func addTextView() {
let textContainer = NSTextContainer(size: CGSize(width: 200, height: CGFloat.greatestFiniteMagnitude))
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainer)
let textStorage = NSTextStorage()
textStorage.delegate = self
textStorage.addLayoutManager(layoutManager)
let textViewFrame = CGRect(x: 50, y: 50, width: 200, height: 200)
let textView = UITextView(frame: textViewFrame, textContainer: textContainer)
textView.backgroundColor = .green
textView.delegate = self
self.view.addSubview(textView)
}
}
extension ViewController: UITextViewDelegate {
func textViewDidBeginEditing(_ textView: UITextView) {
print("text editing began")
}
func textViewDidChange(_ textView: UITextView) {
print("text view content changed")
}
func textViewDidChangeSelection(_ textView: UITextView) {
print("text view selection changed")
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
print("should change \(text) in \(range)")
return true
}
func textViewDidEndEditing(_ textView: UITextView) {
print("text editing ended")
}
}
extension ViewController: NSTextStorageDelegate {
func textStorage(_ textStorage: NSTextStorage, willProcessEditing editedMask: NSTextStorage.EditActions, range editedRange: NSRange, changeInLength delta: Int) {
print("will process \(editedRange)")
}
func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorage.EditActions, range editedRange: NSRange, changeInLength delta: Int) {
print("did process \(editedRange)")
}
}
EDIT 1:
I have a separate storage mechanism on which I update the changes made to the text view based on the range and replacement text provided in shouldChangeText delegate method. I haven't included that for the sake of simplicity.
I just noticed the exact same thing and struggled for quite a while trying to figure this out in an elegant way. It seems to me that this has to be a bug, so hopefully it will be fixed soon. I'll file a bug report with Apple after I submit the solution I came up with.
First, we need a way to keep track of when the user might have deleted an entire word (i.e., anytime they deleted anything we have to check in case they deleted more than just one character). We can do that in the UITextViewDelegate
method textView(_:shouldChangeTextIn:replacementText:)
. We know that something was deleted when the replacement text is empty (i.e., text.count == 0
).
// Variable to keep track of when a word might have been deleted
fileprivate var didJustDelete = false
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text.count > 0 {
// Code to handle adding text
...
} else {
// Code to handle deleting text
didJustDelete = true
...
}
}
Then, in textViewDidChange
(another UITextViewDelegate
method) we can determine the difference and handle it accordingly.
func textViewDidChange(_ textView: UITextView) {
// Check for deleting an entire word
if didJustDelete {
didJustDelete = false
let difference = actualText.count - textView.text.count
if difference > 0 {
// Code to handle deleting a word
let range = NSRange(location: textView.text.count, length: difference)
...
}
}
}