Observe ALL UITextView text changes in realtime

Hi there.

I want to listen for every text change in UITextView. The setup is very trivial.

Code Block
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(
self,
selector: #selector(textViewDidChangeWithNotification(_:)),
name: UITextView.textDidChangeNotification,
object: nil
)
}
@objc private func textViewDidChangeWithNotification(_ notification: Notification) {
print("Text: \(String(describing: inputTextView.text))")
}


It works OK in most cases, but then I have found some UITextInput's black box magic.

Step 1: Type 'I'.

Step 2: Important step. Select all text with double tap on the field.

Step 3: Select 'If' from word suggestions.

And there will be no notification for that new 'If'. This is important part for the task I have. On the other side if the caret will be at the end of the previously typed 'I' and we select 'If' then I receive notification about that change. So the difference is only in tapping 'If' suggestion when word is fully selected or not selected.

Is there any way to observe ALL text changes?

Of course I know that I can get text by using:

Code Block
func textViewDidEndEditing(_ textView: UITextView)


but I need to observe all changes in real time as user types. The other option I always see is to use:

Code Block
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool


but this method is a bad practice to observe changes. If you type two
spaces repeatedly iOS will replace first space with dot and obviously
will not inform you about this action in this method and a lot of other
problems with it.

So is there any way to observe ALL text changes?

Accepted Reply


OK, after a lot of research I've tried RxSwift because I thought that observing text in reactive paradigm framework should succeed in 100% cases. And it worked without any issues!

Code Block
inputTextView.rx.text.subscribe(onNext: { string in
print("rx: \(string)")
})


So it seems that these guys have found the solution.

https://github.com/ReactiveX/RxSwift/blob/master/RxCocoa/iOS/UITextView%2BRx.swift

And here is the solution that gives you information about all text changes despite of auto correction, text selection, and etc..

Code Block
class ViewController: UIViewController {
@IBOutlet weak var inputTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
inputTextView.textStorage.delegate = self
}
}
extension ViewController: NSTextStorageDelegate {
func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorage.EditActions, range editedRange: NSRange, changeInLength delta: Int) {
print("string: \(textStorage.string)")
}
}


Replies

Hello, first of all have you tried with the delegate textViewDidChange?
https://developer.apple.com/documentation/uikit/uitextviewdelegate/1618599-textviewdidchange
Probably this is going to act the same as your Notification, but worth a try.
Otherwise the only thing I would see is to use the
Code Block
shouldChangeTextIn
and fixing the problem it has doing so:
Code Block
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let char = string.cString(using: String.Encoding.utf8)!
let isBackSpace = strcmp(char, "\\b")
var currentText = ""
if (isBackSpace == -92) {
currentText = textField.text!.substring(to: textField.text!.index(before: textField.text!.endIndex))
}
else {
currentText = textField.text! + string
}
return true
}

This removes the backspaced string from your currentText as well as adds it if you have typed it in.



Yeah, I tried delegate approach, it works the same as Notifications.

I've described problem with shouldChangeTextIn. iOS auto correction won't inform you about it's work in shouldChangeTextIn. For example when you double tap space button, probably you will see dot instead of first space and this information about dot insertion doesn't go to shouldChangeTextIn.

OK, after a lot of research I've tried RxSwift because I thought that observing text in reactive paradigm framework should succeed in 100% cases. And it worked without any issues!

Code Block
inputTextView.rx.text.subscribe(onNext: { string in
print("rx: \(string)")
})


So it seems that these guys have found the solution.

https://github.com/ReactiveX/RxSwift/blob/master/RxCocoa/iOS/UITextView%2BRx.swift

And here is the solution that gives you information about all text changes despite of auto correction, text selection, and etc..

Code Block
class ViewController: UIViewController {
@IBOutlet weak var inputTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
inputTextView.textStorage.delegate = self
}
}
extension ViewController: NSTextStorageDelegate {
func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorage.EditActions, range editedRange: NSRange, changeInLength delta: Int) {
print("string: \(textStorage.string)")
}
}


Code Block swift
func textViewDidChange(_ textView: UITextView) {
    print(textView.text!)
  }
}

just use the textViewDidChange(_ textView: UITextView) delegate method that is part of the UITextViewDelegate. this method is called after each little change.
documentation