So you have a UISearchBar
like the one shown below.
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. 🐺