RxSwift course - RxCocoa and UI binding
RxCocoa brings UIKit controls and views to the reactive world. We can access them with .rx
on view object. Neary all relevant properties of views have their reactive wrapper: alpha, background color, frame, visibility, and many more. There are specific extensions for labels, table views, text input, buttons, and more.
In this tutorial, I'll show you how easy is to use RxSwift with UIKit. We will start with a sample iOS app available here. It contains a simple UI - one text field and one button.
Most interesting for us is the setupRx()
method. In three lines of code, we are "connecting" text inputs text property to buttons title property. It will change the button title every time the text inside input changes. No delegates, protocol implementation, extensions. Just these three lines. Isn't it awesome and very readable? You always know what is going on.
bind(...) and binder
Bind operator is in simple words, just fancy subscribe. It will pass events to the receiver.
Binder is a special Rx observer. Binder will crash the application if an error occurs - all views Rx properties are binders, nothing UI-related should generate an error, and errors should be already handled before passing them to view. This ensures a smooth user experience.
The code
Run the application and play with the input. Our button changes the title every time you input a new letter, but it is making the button flash and re-render every time! We can fix that. Modify setupRx()
method:
simpleInput.rx.text
.throttle(.milliseconds(500), scheduler: MainScheduler.instance)
.bind(to: simpleButton.rx.title())
.disposed(by: disposeBag)
I'm introducing a new operator - throttle
- it will emit the first and last value during the given time interval. In our case - it will emit the first letter, then the last available value in the text input every half a second. This should help a lot with flickering.
Let's add additional functionality - the button will be enabled only if the characters count is even. I'll refactor setupRx()
method once more:
private func setupRx() {
let throttledInput = simpleInput.rx.text
.throttle(.milliseconds(500), scheduler: MainScheduler.instance)
throttledInput
.bind(to: simpleButton.rx.title())
.disposed(by: disposeBag)
throttledInput
.map { ($0?.count ?? 0) % 2 == 0 }
.bind(to: simpleButton.rx.isEnabled)
.disposed(by: disposeBag)
}
I extracted throttling to the separate variable - to avoid code repetition. Another few lines of code and we have new functionality!
But the real power of RxCocoa can be easily shown with the table view. I'll rewrite the view controller to only have table view:
class ViewController: UIViewController {
private let tableView = UITableView()
private let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
private func setupUI() {
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: view.topAnchor),
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor)])
}
}
The base implementation is done, we need some data source to populate cells. I'll create a new swift file that will contain a simple string array for demonstration:
Now we have view and data source, now all we need is to display the data in the table view - add setupRx()
method to view controller:
private func setupRx() {
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
Observable.just(dataSource)
.bind(to: tableView.rx.items(cellIdentifier: "cell")) { _, text, cell in
cell.textLabel?.numberOfLines = 0
cell.textLabel?.text = text
}
.disposed(by: disposeBag)
}
And this is it. Zero data sources, delegates, extensions, additional code. In less than 10 lines of code, we have a functional table view! Remember to add setupRx()
below setupUI()
and run the project. The table view will be populated with our simple data source.
And that's it. This base knowledge should cover most UI-related topics. Next time I'll make a real-life use case with REST API and consume JSON data. See you next time!
Here you can find all RxSwift course posts: