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:

let dataSource = ["Lorem ipsum dolor sit amet, consectetur adipiscing elit",
                  "In consectetur arcu sit amet ex pharetra, eget maximus lorem volutpat.",
                  "Integer quis tellus sagittis, rhoncus leo nec, placerat metus.",
                  "Etiam sit amet elit ac lacus pellentesque dictum non sed est.",
                  "Integer imperdiet ipsum molestie, lacinia nisi vel, blandit augue.",
                  "Maecenas pulvinar augue vitae metus auctor, a tristique purus molestie.",
                  "Quisque non urna consequat, aliquam augue eu, semper nisi.",
                  "Donec sed arcu sodales, facilisis lorem sit amet, porttitor ligula.",
                  "Morbi gravida mauris sit amet felis convallis efficitur.",
                  "Proin et urna vitae odio scelerisque egestas in a dui.",
                  "Integer a felis in turpis porttitor ultrices in quis velit.",
                  "Etiam et nisl commodo, consequat ipsum pellentesque, iaculis tortor.",
                  "Maecenas sed velit quis quam sollicitudin accumsan.",
                  "Praesent in turpis sit amet nisi egestas auctor.",
                  "Pellentesque sed velit vel sapien tristique cursus.",
                  "Nunc maximus augue sit amet iaculis elementum."
]
DataSource.swift

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:

RxSwift course
I’ll put here all RxSwift related posts! RxSwift course - basicsFor 3 years I’m working with RxSwift on daily basis. It helps a lot with data manipulation and UI binding when MVVM is your architecture of choice. The framework gives developers flexibility and extendability. I made even an Rx