Simplify updating table view items using automatic diffing on UI Data Sources

UITableViewDiffableDataSource iOS 13 API

Aaina jain
5 min readMay 4, 2020

In existing table view, while doing insertion, deletion or moving rows, we need to call beginUpdates() or endUpdates() or performBatchUpdates() otherwise we end up in getting following error

To focus us on data source [it could be web service or core data] and design, Apple came up with Declarative approach to UI state in which diffable data source API will take care of UI state and animating differences. In existing table view system, there are 2 source of truth, our data source and UI state which might not be in sync always. Now with declarative approach we have UI state which gets updated on applying new snapshot if there is difference. In new approach we need to call apply() on data source which is simple and performs automatic diffing. Diffable data source provide us item identifier and section identifier so we no longer need to deal with indexPath to get current row item while showing cell. In existing apis, like didSelectRowAt current row item can be get by itemIdentifier(for:)

I have built a Quotes app which loads quotes from json, group them by author and show them in table view. It performs other operations as well like add quote, shuffle sections and delete quote from list.

Let’s get overview of classes built in this app:

  • QuotesPresenter - Responsible for loading data from json and apply logic to group quotes by author. It creates data in this format: [Author: Set<QuoteViewData>]. It also add new quote on demand.
  • QuotesViewController - After getting data from presenter, it asks QuotesView to apply snapshot on data source.
  • QuotesView - Creates table view, register cells. Configure diffable data source and apply snapshot when get request from view controller.

Apple came up with generic parameterised class UITableViewDiffableDataSource

This class takes two generic types SectionIdentifierType and ItemIdentifierType which must conform to the Hashable protocol in order to be uniquely identified.

This class provides an initializer which take table view as input and cellProvider is an escaping closure in which we need to handle cell dequeuing and configuration operations, as we used to do in cellForRow(at:) function.

This snippet configures data source for quotes view. If you want to implement other data source functions of existing api then you need to subclass UITableViewDiffableDataSource in my case I have created QuotesViewDiffableDataSource .

SectionIdentifierType represents the type of our sections and ItemIdentifierType represents data model. Both identifier types must be hashable. If all types used in model are hashable, then you just need to conform model to hashable and it will take care of creating hash value for you.

But if you want to modify logic to generate hash value then you need to implement hash(into:) like this:

struct Jedi: Hashable {
var name: String
var darkSideUsage: Int
func hash(into hasher: inout Hasher) {
hasher.combine(name)
hasher.combine(darkSideUsage)
}
}

This example is taken from HackingWithSwift website.

Now our data source is ready, to consume it we need to apply snapshot.

SectionIdentiferType and ItemIdentifierType must match to types specified in configureDataSource.

I have created snapshot instance and then appended all sections to it. Then it’s iterating over dictionary to append items specific to section. Once all data is added snapshot is applied by data source with animating differences is set to true. While loading table content first time, there is no difference in snapshot so you may want to set animatingDifferences to false initially.

What is Snapshot?

  • Snapshot is a source of truth of UI state
  • It creates unique identifiers for sections and items.

Now while performing shuffle, I have shuffled sections.

Now what it will do is it will create new snapshot and will ask data source to apply this snapshot. data source will compare this snapshot with existing snapshot, will modify content based on differences and animate it nicely. Key point is data applied should be unique otherwise it will crash.

Snapshot instance can be accessed in all data source apis.

data for all above apis is provided by snapshot() which is our source of truth.

Apple has said that apply function can be call from any queue background or main but it’s recommended to always call from same queue either background or foreground.

On loading this app, I get this warning in console. I searched about this over internet but didn’t find any fix, looks like apple needs to fix it.

[TableView] Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information (e.g. table view bounds, trait collection, layout margins, safe area insets, etc), and will also cause unnecessary performance overhead due to extra layout passes. Make a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy to catch this in the debugger and see what caused this to occur, so you can avoid this action altogether if possible, or defer it until the table view has been added to a window. Table view: <UITableView: 0x7feb8e013c00; frame = (0 0; 0 0); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x60000229ae20>; layer = <CALayer: 0x600002cd49e0>; contentOffset: {0, 0}; contentSize: {0, 0}; adjustedContentInset: {0, 0, 0, 0}; dataSource: <Quotes.QuotesViewDiffableDataSource: 0x600002e945b0>>

https://www.hackingwithswift.com/forums/swiftui/problems-with-tabview/111/182

UICollectionView also supports DiffableDataSource in same way. To get more details on it, worth to watch wwdc video.

Repository link of Quotes app: https://github.com/aainaj/quotes-tableview-diffable-data-source

You can reach out to me at:

Linkedin: Aaina Jain

Twitter: __aainajain

--

--