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.