Chapters

Hide chapters

RxSwift: Reactive Programming with Swift

Fourth Edition · iOS 13 · Swift 5.1 · Xcode 11

18. Table & Collection Views
Written by Florent Pillet

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

The most frequent requirement for iOS applications is to display content in table or collection views. A typical implementation features two or more data source and delegate callbacks, although you often end up with more. RxSwift not only comes with the tools to perfectly integrate observable sequences with tables and collections views, but also reduces the amount of boilerplate code by quite a large amount.

Basic support for UITableView and UICollectionView is present in the RxCocoa framework you were introduced to in previous chapters.

In this chapter, you’ll learn how to quickly wire up tables and collections with just the built-in framework tools. Extended support for things such as sections and animations comes with RxDataSources https://github.com/RxSwiftCommunity/RxDataSources, an advanced framework found under the umbrella of RxSwiftCommunity organization.

The examples below are for UITableView, but the same patterns work for UICollectionView as well.

Basic table view

In a typical scenario, you want to display a list of items of the same type: for example, a list of cities, as you saw in previous chapters. Using standard cells to display them requires nearly zero setup. Consider a single observable list of cities:

@IBOutlet var tableView: UITableView!

func bindTableView() {
  let cities = Observable.of(["Lisbon", "Copenhagen", "London", "Madrid", "Vienna"])

  cities
    .bind(to: tableView.rx.items) {
      (tableView: UITableView, index: Int, element: String) in
      let cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
      cell.textLabel?.text = element
      return cell
    }
    .disposed(by: disposeBag)
}

And. That’s. All. You don’t even need to set your UIViewController as a UITableViewDataSource. Wow!

This deserves a quick overview of what’s going on:

  • tableView.rx.items is a binder method operating on observable sequences of elements (like Observable<[String]>).
  • The binding creates an invisible ObserverType object which subscribes to your sequence, and sets itself as the dataSource and delegate of the table view.
  • When a new array of elements is delivered on the observable, the binding reloads the table view.
  • To obtain the cell for each item, RxCocoa calls your closure with details (and date) for the row being reloaded.

This is straightforward to use. But what if you want to capture the user selection? Again, RxCocoa is here to help:

tableView.rx
  .modelSelected(String.self)
  .subscribe(onNext: { model in
    print("\(model) was selected")
  })
  .disposed(by: disposeBag)

The modelSelected(_:) method returns an observable which emits the model object (the element represented by the cell) every time the user selects one. An additional variant — itemSelected() — transports the IndexPath of the selected item.

RxCocoa offers a number of observables:

  • modelSelected(_:), modelDeselected(_:), itemSelected, itemDeselected fire on item selection.
  • modelDeleted(_:) fires on item deletion (upon tableView:commitEditingStyle:forRowAtIndexPath:).
  • itemAccessoryButtonTapped fire on accessory button tap.
  • itemInserted, itemDeleted, itemMoved fire on event callbacks in table edit mode.
  • willDisplayCell, didEndDisplayingCell fire every time related UITableViewDelegate callbacks fire.

These are all simple wrappers around equivalent UITableViewDelegate methods.

Multiple cell types

It’s nearly as easy to deal with multiple cell types.

enum MyModel {
  case text(String)
  case pairOfImages(UIImage, UIImage)
}

let observable = Observable<[MyModel]>.just([
  .textEntry("Paris"),
  .pairOfImages(UIImage(named: "EiffelTower.jpg")!, UIImage(named: "LeLouvre.jpg")!),
  .textEntry("London"),
  .pairOfImages(UIImage(named: "BigBen.jpg")!, UIImage(named: "BuckinghamPalace.jpg")!)
])
observable.bind(to: tableView.rx.items) {
  (tableView: UITableView, index: Int, element: MyModel) in
  let indexPath = IndexPath(item: index, section: 0)
  switch element {
  case .textEntry(let title):
    let cell = tableView.dequeueReusableCell(withIdentifier: "titleCell", for: indexPath) as! TextCell
    cell.titleLabel.text = title
    return cell
  case let .pairOfImages(firstImage, secondImage):
    let cell = tableView.dequeueReusableCell(withIdentifier: "pairOfImagesCell", for: indexPath) as! ImagesCell
    cell.leftImage.image = firstImage
    cell.rightImage.image = secondImage
    return cell
  }
}
.disposed(by: disposeBag)

Providing additional functionality

Even though RxCocoa-driven table views and collection views don’t require that you set up your view controller as a delegate, you can do so to provide complementary functionality not managed by RxCocoa extensions.

tableView.rx
         .setDelegate(myDelegateObject)
         .disposed(by: disposeBag)

RxDataSources

RxCocoa handles the table and collection view needs of many apps. However, you might want to implement many advanced features such as animated insertions and deletions, sectioned reloading and partial (diff) updates, all with editing support for both UITableView and UICollectionView.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now