Binding to a table view with multiple cells and sections

One of the questions that keeps popping up in the RxSwift Slack channel for years now is how to display multiple cell types when binding data to a table view.

This is actually very easy to do and in this post I’ll show you two distinct ways to display multiple cells in a table view (and it works identically for collection views if that’s what you’re looking for).

We’ll look at the following two use cases:

  • binding items via using RxCocoa’s bind(to:) operator,
  • using RxDataSources to bind cells in different table sections.

Let’s get started!

Easy binding via RxCocoa and bind(to:)

If you do not need multiple table sections or you do not want to add RxDataSources as an extra dependency in your project you can bind directly via bind(to:).

I’ve got a simple table view controller with two separate cells - one is displaying “standard” items in my list and the other “important” ones (the latter is just styled a bit more fancy):

view controller

The two cells have identifiers as follows: “Cell” and “ImportantCell”.

In my view controller’s viewDidLoad(_:) method I’m making an observable of string values to display in the table view:

let items = Observable.just([
  "First", 
  "(New) Second", 
  "(New) Third", 
  "Fourth"
])

The goal here will be to bind this observable to the table and display any items starting with “(New)” via the pre-designed important cell.

Next I’m adding two helper factory methods that will produce standard and important cells when needed:

private func makeCell(with element: String, from table: UITableView) -> UITableViewCell {
  let cell = table.dequeueReusableCell(withIdentifier: "Cell")!
  cell.textLabel!.text = element
  return cell
}

private func makeImportantCell(with element: String, from table: UITableView) -> UITableViewCell {
  let cell = table.dequeueReusableCell(withIdentifier: "ImportantCell")!
  cell.textLabel!.text = element
  cell.textLabel!.textColor = .red
  return cell
}

makeCell instantiates a cell with “Cell” identifier and sets its label to the given string. makeImportantCell similarly makes an instance of “ImportantCell” and also applies some styling to the label.

Now I need to call the correct factory method when binding data. Back in viewDidLoad(_:) I’m adding the binding code:

items.bind(to: tableView.rx.items) { table, index, element in
  if element.hasPrefix("(New)") {
    return self.makeImportantCell(with: element, from: table)
  } else {
    return self.makeCell(with: element, from: table)
  }
}
.disposed(by: bag)

And there you have it - this code binds the items observable sequence to the table view items and provides a factory closure that instantiates each cell that the table will display.

In this example it’s enough to use hasPrefix(_:) to diversify between the cell types to return, but of couse in your own code you can have as complex rules as you need.

Running the code displays my list on screen with the correct cells:

multiple cells

Using multiple data models

More often that not you will have a list of data models (usually some custom structs) instead of a list of strings that you’d like to bind to a table view.

That’s easy enough to as well, define a custom enum with the different data models you will be binding and do a switch in the cell factory closure. For example:

enum MyCellModels {
  case former(FirstModel)
  case latter(SecondModel)
}

Now your observable should emit a sequence of MyCellModels instances and you’re good to go. The next section actually features an example of this approach so we won’t go into more details right here and now.

Binding multiple table sections via RxDataSources

Another use case for binding multiple cell types is when your table features multiple sections - sometimes different sections display different kinds of data and naturally you’ll use different cells to do that.

The default binding via RxCocoa does not support sections but the well known RxDataSources library does that so we’ll have a look at how to use it with multiple cell types.

The concept is similar to what we looked at in the first section of this post but here we’ll take the code a bit further.

For this example I’m going to use exactly the same view controller in my storyboard:

view controller

This time around though I’m going to add RxDataSources to a new view controller class and do things slightly differently.

First I’m going to model my data. In this example the standard items in my table are going to be driven by a list of strings, but the important items are going to have a dedicated data model struct called Important. (This is done mostly so that I can show you how to model more compelx data bindings.)

Let’s start with Important:

struct Important {
  let text: String
  let imporance: Int
}

The data model for each important cell in the table includes a text to display on screen and an importance level (an arbitrary number for demo purposes).

Next I’m going to build the type for my observable sequence, which should accomodate for either String items or Important items:

enum CellModel {
  case standard(String)
  case important(Important)
}

Now I can create a sequence of type CellModel and bind it to the table view.

I’m going to copy my cell factory methods from the previous section, but adjust the latter one to accomodate for displaying the custom Important model like so:

private func makeCell(with element: String, from table: UITableView) -> UITableViewCell {
  let cell = table.dequeueReusableCell(withIdentifier: "Cell")!
  cell.textLabel!.text = element
  return cell
}

private func makeImportantCell(with element: Important, from table: UITableView) -> UITableViewCell {
  let cell = table.dequeueReusableCell(withIdentifier: "ImportantCell")!
  cell.textLabel!.text = element.text
  cell.textLabel!.textColor = .red
  return cell
}

With all that prep finished let’s move to the view controller’s viewDidLoad(_:) where I’m going to create the observable and bind it to the table view.

First I’m going to create the observable:

let sections = Observable.just([
  SectionModel(model: "Standard Items", items: [
      CellModel.standard("First item"),
      CellModel.standard("Second item")
  ]),
  SectionModel(model: "Important Items", items: [
      CellModel.important(.init(text: "Third item", imporance: 1)),
      CellModel.important(.init(text: "Fourth item", imporance: 100))
  ])
])

When using RxDataSources to bind sectioned data you need to emit section models. You can create your own section model types or (as above) you can use the example, pre-defined SectionModel.

Each SectionModel in the list contains a section name and a list of CellModel items.

The first section contains CellModel.standard items and the second section contains CellModel.important items. In case your observable updates the table content dynamically you need to re-emit exactly the same structure each time.

Next I’m going to create my custom data source for the binding:

let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, CellModel>>(configureCell: { dataSource, table, indexPath, item in
  switch item {
  case .standard(let text):
    return self.makeCell(with: text, from: table)
  case .important(let important):
    return self.makeImportantCell(with: important, from: table)
  }
})

I’m creating a RxTableViewSectionedReloadDataSource which is set to emit SectionModel<String, CellModel> items. In the cell factory closure I just use a switch over each item and return the cell appropriate for displaying each item.

Additionally to make the output a bit easier to understand let’s configure the headers for each section as well:

dataSource.titleForHeaderInSection = { dataSource, index in
  return dataSource.sectionModels[index].model
}

With the data source ready, the last bit is to actually bind the observable to the table view:

sections
  .bind(to: tableView.rx.items(dataSource: dataSource))
  .disposed(by: bag)

And here’s the result in the iPhone Simulator:

binding with data sources

As you saw, when you ignore all the setup and data code the actual binding is just few lines - RxDataSources really shines when it comes to handling more complex data bindings.

Where to go from here?

No matter if you are looking for an easy and quick binding via RxCocoa’s built-in bind(to:) or binding a more complex data model via RxDataSources, it’s always just few lines of code! I hope you enjoyed this post!

In case you are interested in binding data from the Realm database specificaly, I got some posts in here that cover this as well.

To learn more about RxSwift and testing check out the RxBook! The book is available at http://raywenderlich.com/store - this is where you can see all updates, discuss in the website forums, etc.

Hope that post was helpful, and if you want to get in touch you can find me here

Share this post:


If you'd like to learn how to create professional production apps with RxSwift, the best resource out there is the RxSwift book written by Florent Pillet, Junior Bontognali, Marin Todorov, & Scott Gardner.

It features 20+ chapters covering the basics, the Rx operators, and advanced topics like testing, error handling, and app architecture.

Available from Ray Wenderlich: » Learn more.
More Reading