12

I noticed that

textField.publisher(for: \.text)

delivers events when editing finishes, but not for every character/editing change. How do I get a Publisher, that sends evens for every change? In ReactiveSwift it would be

textField.reactive.continousTextValues()

And in RxSwift it would be just (How do you get a signal every time a UITextField text property changes in RxSwift)

textField.rx.text

Approaches I took:

  • checking the publisher(for:options:) method, but there are no appropriate options for the desired outcome.
  • adding a target/action textField.addTarget(self, action: #selector(theTextFieldDidChange), for: .editingChanged) (UITextField text change event)
  • doing essentially the same as the previous step by connecting the action via interface builder, both leading to extra work and cluttered code.
  • Watching the 2019 WWDC videos about Combine. They were not dealing with textfields, but using @Published variables instead, hiding were the values actually came from - (or did I miss something?).

I have no clue at the moment, how to do this and I feel the tendency to go back to ReactiveSwift, but I wanted to ask you, before taking this step backwards.

thetrutz
  • 1,395
  • 13
  • 12

4 Answers4

16

In case of a UITextField you can use this extension:

  extension UITextField {
    func textPublisher() -> AnyPublisher<String, Never> {
        NotificationCenter.default
            .publisher(for: UITextField.textDidChangeNotification, object: self)
            .map { ($0.object as? UITextField)?.text  ?? "" }
            .eraseToAnyPublisher()
    }
  }

use as: textField.textPublisher()

Sam Soffes
  • 14,831
  • 9
  • 76
  • 80
kprater
  • 439
  • 4
  • 4
6

Unfortunately, Apple didn't add publishers for this to UIKit and because UIControl doesn't implement KVO the publisher(for:) publisher doesn't work quite as expected. If you're okay with adding a dependency to your project there are a couple of good UIControl publishers here.

Alternatively, you could implement your own publisher that does this, or subscribe to textfield changes the old school way.

donnywals
  • 7,241
  • 1
  • 19
  • 27
6

If this is what you are looking for, I guess UITextField emits both notifications and controlevents, so you can achieve that by listening to notifications for example:

import UIKit
import Combine

class ViewController: UIViewController {

    @IBOutlet private var textField: UITextField!

    var cancellables = Set<AnyCancellable>()

    override func viewDidLoad() {
        super.viewDidLoad()

        let textFieldPublisher = NotificationCenter.default
            .publisher(for: UITextField.textDidChangeNotification, object: textField)
            .map( {
                ($0.object as? UITextField)?.text
            })
        
        textFieldPublisher
            .receive(on: RunLoop.main)
            .sink(receiveValue: { [weak self] value in
                print("UITextField.text changed to: \(value)")
            })
            .store(in: &cancellables)
    }
}

Let me know if this was your actual goal.

denis_lor
  • 6,212
  • 4
  • 31
  • 55
4

You can always create a custom Publisher for your needs. For example, here I've created TextField publisher, that wraps textFieldDidChange action for textField and sends String after each character entered/deleted! Please, copy the link, SO doesn't parse it:

https://github.com/DmitryLupich/Combine-UIKit/blob/master/CombineCustomPublishers/%20Publishers/TextFieldPubisher.swift

Dmitriy Lupych
  • 609
  • 7
  • 9