thetrutz
thetrutz

Reputation: 1545

Swift Combine - How to get a Publisher that delivers events for every character change of UITextField's text property

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:

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.

Upvotes: 12

Views: 12634

Answers (4)

kprater
kprater

Reputation: 633

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()

Upvotes: 16

denis_lor
denis_lor

Reputation: 6547

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.

Upvotes: 6

donnywals
donnywals

Reputation: 7591

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.

Upvotes: 6

Dmitriy Lupych
Dmitriy Lupych

Reputation: 639

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

Upvotes: 5

Related Questions