Sarath Raveendran
4 min readAug 1, 2020

--

Diffable data source an iOS 13 API for UITableView and UICollectionView — Part 2

Datasources provide data to collection views and tableviews, which are responsible for rendering each item. When the data is updated, the UI is updated through reloadData() or with preformBatchUpdates(_:completion).

Unfortunately, these methods are common source for bugs.

  1. reloadData() updates the entire UI, providing bad user experience.
  2. preformBatchUpdates(_:completion) is useful but easily gets complex.

Diffable data sources now take care of a lot of these issues through automatic diffing with apply(_:animatingDifferences:). The data source creates a snapshot of the current UI state which is compared to the newly presented snapshot. It then diffs and updates the UI with a new snapshot state.

Please go though the part 1 of this series for fundamental ideas.

Today we are going to manage collection view with diffable data source. This series is more focused on coding part so download the project from the repo.

To create a collection view through new approach, we follow

  1. Connect a diffable data source to your collection view.
  2. Implement a cell provider to configure your collection view’s cells.
  3. Generate the current state of the data.
  4. Display the data in the UI.

Let’s see the code how we achieve these steps,

typealias DiffableDataSource = BooksDiffableDataSource
lazy var dataSource = createDataSource()
func createDataSource() -> DiffableDataSource {

let dataSource = DiffableDataSource(collectionView: self.collectionView) { (collectionView, indexPath, book) -> UICollectionViewCell? in

let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BookCollectionViewCell", for: indexPath) as! BookCollectionViewCell
cell.data = book
return cell
}
return dataSource
}
  1. First 2 lines declare the data source, it is a type of UICollectionViewDiffableDataSource<Section, TextBook>. Here we subclassed as BooksDiffableDataSource for enhanced usage.
  2. The createDataSource method creates a datasource which is a closure and with in the closure we define the cell. We can compare it as our cellForRowAtIndexPath

Providing state of data means insert the actual data to the UI. In our case we point the data (an json array) to the diffable data source.

func setData(_ animate: Bool) {

var snapShot = NSDiffableDataSourceSnapshot<Section, TextBook>()

// Set Section
snapShot.appendSections(books)

// Set Data For Section
books.forEach { section in

snapShot.appendItems(section.books, toSection: section)
}

// Push
dataSource.apply(snapShot, animatingDifferences: animate, completion: nil)
}

The steps are

  1. load our json data and store it a variable.
  2. Create a snapshot of the diffable data source then append the section to snapshot. Even If we don’t have section separation add at least one. Then add data for the section.
  3. Finally apply the snapshot by calling apply method. The data will automatically set to the UI.

And finally warp these codes in viewDidLoad. At this point your collection view will display data perfectly.

The actual advantage of diffable comes when we perform operations on data source. It manage smooth auto animation and error free process. To demonstrate the same we covered following operations in our project

  1. Search
  2. Filter
  3. Update
  4. Deletion
  5. Empty state

Go through the code and you can reach me in case of doubts.

Additional situations that we have to care,

  1. how to manage the cell selection. Which is almost same as our conventional approach.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {guard let selectedItem = dataSource.itemIdentifier(for: indexPath) else { return }// Here you go with your selection}

We confirm our CollectionViewDelegate and will override our didSelectItemAt method. And we ask dataSource to provide selected item.

2. How to manage supplementary view, it can be managed with the data source itself. In our project we manage header view.

// HEADER
dataSource.supplementaryViewProvider = { collectionView, kind, indexPath in

guard kind == UICollectionView.elementKindSectionHeader else { return nil }
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "BookSectionHeaderViewCell", for: indexPath) as! BookSectionHeaderViewCell
let section = dataSource.snapshot().sectionIdentifiers[indexPath.section]
view.data = section.name
return view
}

Here we add supplementary view as a closure. And data we fetch from current snapshot for view.

3. How to handle multiple cell types, this also managed by the datasource where we register our cell. All we have do is classify cell by indexPath.section or whatever is your logic is. The display will be managed by the diffable api.

let dataSource = DiffableDataSource(collectionView: self.collectionView) { (collectionView, indexPath, book) -> UICollectionViewCell? in

if indexPath.section == 2 {

let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BookCollectionViewCellType2", for: indexPath) as! BookCollectionViewCellType2
cell.data = book
cell.delegate = self
return cell
} else {

let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BookCollectionViewCell", for: indexPath) as! BookCollectionViewCell
cell.data = book
cell.delegate = self
return cell
}
}

4. How to handle Multiple DataTypes, you must use <AnyHashable> instead of concrete type.

var snapShot = NSDiffableDataSourceSnapshot<AnyHashable, AnyHashable>()var dataSource = UICollectionViewDiffableDataSource<AnyHashable, AnyHashable>
dataSource = UICollectionViewDiffableDataSource<Section<AnyHashable, [AnyHashable]>, AnyHashable>(collectionView: collectionView) { (collectionView, indexPath, item) -> UICollectionViewCell? in

if let media = item as? Movie {

let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MediaCell", for: indexPath)
cell.data = media
return cell
}

if let categorey = item as? Categorey {

// Configure category cell
}

return nil
}

This is all for now, if you have better approaches in the current implementation, please do post. I appreciate your time.

Thanks for reading. Happy coding.

--

--