2

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

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: 'I' typed. We can see 'I' in the output.
Step 2: Important step. Select all text with double tap on the field.
Step 3: Select 'If' from word suggestions.

enter image description here

And there is no 'If' in the debuggers output. On the other side if the caret will be at the end of the 'I' word and we select 'If' output results are correct.

enter image description here

Is there any way to observe ALL text changes?

I can get text by using:

func textViewDidEndEditing(_ textView: UITextView)

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

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.

evilgeniuz
  • 346
  • 5
  • 18

3 Answers3

5

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!

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..

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)")
    }
}
evilgeniuz
  • 346
  • 5
  • 18
2

You can use func textViewDidChangeSelection(_ textView: UITextView) to detect the change in selection ?

King.lbt
  • 843
  • 5
  • 15
  • 1
    Yeah, the result is the same as using NotificationCenter and UITextView.textDidChangeNotification. – evilgeniuz Jun 17 '20 at 13:16
  • 1
    I have strange feeling about this. This actually gives me what I need, I can get correct text after caret position did change, but from the name of the function I can't stop thinking that observing text changes in that place is a side effect and someday it will play a trick. But anyway thanks for the help, I'll try to experiment with this approach for now. – evilgeniuz Jun 18 '20 at 22:37
0

If you want to listen for every change shouldChangeTextIn is the way to go. You can write conditions to solve the problems associated with it

MNG MAN
  • 69
  • 1
  • 4
  • Hi. Unfortunately not everything goes to the 'shouldChangeTextIn'. In my question I describe case when you type two spaces repeatedly, in that case iOS will replace first space with dot and this text change will not go to 'shouldChangeTextIn'. Also there is a problem with addiitonal space insertion. I can't know is it space that was typed by user or is it iOS auto correction. – evilgeniuz Jun 17 '20 at 13:18