So you have a UISearchBar like the one shown below.

uisearchbar

A user typing into a search bar is an ideal input data stream for Combine.

Ideally, we’d access a string publisher based on user input like so.

let searchBar = UISearchBar()
let searchText = searchBar.textPublisher

Alas, it does not exist.

I also tried performing Key-Value Observing with Combine on the searchBar or its text field.

searchBar.publisher(for: \.text)
searchBar.searchTextField.publisher(for: \.text)

It compiles but emits no events.

Neither UISearchBar nor the UITextField class has built-in support for KVO on the text property.

So we need a workaround for this common scenario.

Swift Combine UISearchBar Workarounds

1. Use NotificiationCenter Publisher with UITextField

UISearchBar has a property searchTextField of type UISearchTextField that inherits from UITextField. We can extend UITextField and use NotificationCenter to observe the textDidChangeNotification, map to the text from the text field and emit the result.

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

Now we can subscribe to it.

searchBar
    .searchTextField
    .textPublisher
    .sink { text in
        // do something with the text
    }

2. Use addTarget and PassthroughSubject with UITextField

We could use the searchTextField and addTarget method to point to a function to run when the .editingChanged events occur. Then in the function, we can emit the changed text via a PassthroughSubject.

Store a passthrough subject on your view or view controller.

let textSubject = PassthroughSubject<String, Never>()

Create a function to target when the text field is edited.

@objc func textDidChange(_ searchBar: UISearchBar) {
    textSubject.send(searchBar.text ?? "")
}

Add the target to the search bar.

searchBar.searchTextField.addTarget(self, action: #selector(textDidChange), for: .editingChanged)

Subscribe to the subject.

textSubject
    .sink { text in
        // do something with the text
    }

3. Use UISearchBarDelegate and PassthroughSubject

Similar to above, we can set the UISearchBarDelegate and receive updates through the textDidChange delegate method, then emit values through our subject.

Store a passthrough subject on your view or view controller.

let textSubject = PassthroughSubject<String, Never>()

Conform to the UISearchBarDelegate protocol and implement the textDidChange method.

extension ViewController: UISearchBarDelegate {
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        textSubject.send(searchBar.text ?? "")
    }
}

Assign the delegate to the conforming object, in this case, we use self.

searchBar.delegate = self

Subscribe to the subject.

textSubject
    .sink { text in
        // do something with the text
    }

4. Use CombineCocoa Framework

If you’re willing to import a framework, CombineCocoa is a fantastic framework from the CombineCommunity that provides publishers for common UIKit control.

For UISearchBar, CombineCocoa provides a textDidChangePublisher property.

Simply add CombineCocoa as a dependency through your dependency manager (SPM, CocoaPods, Carthage etc.), then import CombineCocoa.

Now you can access textDidChangePublisher like so.

searchBar
    .textDidChangePublisher
    .sink { text in
        // do something with the text
    }

5. Rx Compatible Version with RxCombine

Another great framework for reactive programming with UIKit is RxCocoa. If you happen to be using this and want to bridge to Combine the CombineCommunity also provide the RxCombine library. You can use it by importing the frameworks.

import RxCocoa
import RxCombine

Then use the rx.text property to Observable of the text, then bridge to Combine with the .publisher property.

searchBar.rx.text
    .publisher
    .replaceError(with: nil)
    .sink { string in
        // do something with the text
    }

Conclusion

It’s a shame Combine doesn’t have a convenient way to create a text publisher from a UISearchBar. Nevertheless, there are a few small workarounds that can get you there.

There are a few options that do not require additional dependencies. If you choose to extend UITextField functionality, you’ll also get a publisher for other text fields.

If you are going to be using UIKit and Combine a lot, then it may be worth considering CombineCocoa.

Good luck out there. 🐺