Implement a UITableView in RxSwift

RxSwift is a reactive programming framework which enables easy composition of asynchronous operations and event/data streams. I’m currently building a Twitter timeline analyzer iOS app called Tweetometer to learn RxSwift. You can check out the code in the repository on Github. In this post I’d like to show how to build a simple UITableView with RxSwift.

Start by creating a new iOS project and set the language to Swift. You now have to install the RxSwift framework. Follow the instructions in the official repository to install it with CocoaPods or Carthage. If you choose CocoaPods, this is what your Podfile should look like.

use_frameworks!

target 'RxTableView' do
    pod 'RxSwift'
    pod 'RxDataSources'
end

Make sure you install RxDataSources which wraps the UITableView and UICollectionView data sources for RxSwift.

We are now able to import our two frameworks in the ViewController class:

import RxSwift
import RxDataSources  

Open the Main.storyboard and drag into the scene a UIViewController object and embed it in a Navigation Controller by doing Editor → Embed In → Navigation Controller. Configure a UITableView with a UITableViewCell with a unique identifier such as Cell. Create an outlet from the UITableView to the ViewController. This is how your Main.storyboard and ViewController.swift files should look like.

import UIKit
import RxSwift
import RxDataSources

class ViewController: UIViewController, UITableViewDelegate {

    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

Great, we are now ready to implement our UITableViewDelegate and UITableViewDataSource methods, right? Well, not really. Since we’re using a different reactive approach, we’re not going to write all those boring methods. We’ll use the power of RxSwift combined with RxDataSources to configure the UITableView.

Let’s create an example. You have a model that consists of a list of users and you want to display them. A User is defined as a struct with a few simple properties:

import Foundation

struct User {
    let followersCount: Int
    let followingCount: Int
    let screenName: String
}

In the ViewController we declare a constant that contains the data source by specifying the type of objects that we will display in our UITableView.

let dataSource = RxTableViewSectionedReloadDataSource>()

This is a RxDataSources class that specifies what our data source contains. It contains a SectionModel with a String as the section name and a User as the item type. You can ⌘ + click on the objects declaration to see the implementation of those objects if you are feeling curious.

We can now create the View Model which will pass the data to our data source. By separating the view model, we avoid to add the the code that takes care of building the model from the ViewController. Create a new class and call it ViewModel.swift. Import RxSwift in the new file.

import UIKit
import RxSwift

class ViewModel {

}

Let’s write the main function that will create the user objects. In your application, you could fetch objects from the network or from a local database in this function. You can see an example on how to load a JSON file from the Twitter APIs in my Tweetometer View Model.

The function getUsers() is defined as follows:

func getUsers() -> Observable<[SectionModel]> {
    return Observable.create { (observer) -> Disposable in    
        let users = [User(followersCount: 1005, followingCount: 495, screenName: "BalestraPatrick"),
                    User(followersCount: 380, followingCount: 5, screenName: "RxSwiftLang"),
                    User(followersCount: 36069, followingCount: 0, screenName: "SwiftLang")]
        let section = [SectionModel(model: "", items: users)]
        observer.onNext(section)
        observer.onCompleted()      
        return AnonymousDisposable{}
    }
}

An Observable is the most important and basic object in reactive programming and it is a sequence of values. That’s why it’s really easy to add an asynchronous operation to fetch your data. You can connect multiple Observables together and wait for those to complete before reloading a UITableView for example.

The Observable we just defined, is an array of SectionModel containing a String and a User. Do you remember this type? It’s exactly what a row of our UITableView data source expects. We insert three users with some example data in an array and we create a single section. We specify an empty String as the name of the Section because we don’t want any section title in this case. If you would like to add a section title, you should add a title in your SectionModel and implement the titleForHeaderInSection method in the ViewController.

We then tell our observer that our sequence of values is finished and we complete our function by calling the .onCompleted() method. We also return a AnonymousDisposable to make sure that our resources are cleaned after the function returns. In the case of a network request, you could for example cancel all the pending requests in the AnonymousDisposable closure. There is a really good explanation of the Sequence and Disposable concepts in this Getting Started guide.

After having created the logic in the ViewModel, we can now go back in the ViewController and connect our data source. First of all, create two now constants holding the ViewModel and a DisposeBag. This is an object that controls the cleaning of the used resources after the ViewController is deallocated.

let viewModel = ViewModel()
let disposeBag = DisposeBag() 

In the viewDidLoad() method, let’s configure a UITableViewCell and bind the ViewModel with the UITableView data source.

override func viewDidLoad() {
    super.viewDidLoad()
    
    dataSource.configureCell = { table, indexPath, user in
        let cell = table.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
        let string = "\(user.screenName) is following \(user.followingCount) users and is followed by \(user.followersCount) users."
        cell.textLabel?.text = string
        cell.textLabel?.numberOfLines = 0
        cell.backgroundColor = indexPath.row % 2 == 0 ? UIColor.whiteColor() : UIColor(red: 0, green: 0, blue: 0, alpha: 0.05)
        return cell
    }
    
    viewModel.getUsers()
        .bindTo(tableView.rx_itemsWithDataSource(dataSource))
        .addDisposableTo(disposeBag)
}

We create a UITableViewCell with the given identifier in Storyboard. The configureCell closure gives the possibility to configure each cell. This is very similar to what you would do in the cellForRowAtIndexPath method. We change the backgroundColor property based on the indexPath and we automatically get the User object for the correct indexPath! ✨

We create a simple String out of the user data and we display it in the cell’s UILabel. This is all the customization we need for this example.

The last thing to do is to bind the Observable returned from the getUsers() function to our data source. As soon as the ViewModel will return a SectionModel object, our UITableView will automatically display the users. Awesome! 🎉

This is the result after running the application.

RxDataSources is very powerful. Instead of using a simple RxTableViewSectionedReloadDataSource we could use a RxTableViewSectionedAnimatedDataSource to animate the data source changes. It even supports UICollectionView.

We could extend this example much more in the future by passing the selected item into a UIStoryboardSegue. The project is available on Github.

This is all I wanted to share in this post, let me know if you have any improvement or feedback on Twitter.

Thanks a lot to @a2 for proofreading this post.