Diffable datasource for heterogeneous items in iOS/Swift

Pattogató Bence
Sep 15 · 5 min read

UICollectionViews are not historically great at understanding what’s happening with their data. They have a data source and when reloadData() is called on them they present whatever data is returned from their delegate methods.

If you want to animate insert/delete/reload/move operations you have these options to choose from:

  • do it manually with the performBatchUpdates method
  • use Swift 5.1 ordered collection diffing
  • back the collection with CoreData and use it’s utility functions

In this article we’re gonna talk about the 4th option which isto use UICollectionViewDiffableDataSource as the data source of your collection view. Given that your items are Hashable, it will figure out the differences between two snapshots of a given data source and do all the animated updates for you.

The iOS Weather app doesn’t let you tap on the upcoming days to show you more refined weather forecasts. I find it unacceptable, so here’s my solution to that, a weather app that tells you random forecasts for as long as you wish, in hourly resolution.

Tapping on a day shows the weather in a hourly resolution

As you can see, the screen is a multi-axis scrolling collection view that displays different items:

  • hourly weather in the first section
  • daily in the second. You can tap on these to reveal the hourly forecast for that day.

(the layout I used is UICollectionViewCompositionalLayout)

  1. How to handle heterogeneous items with UICollectionViewDiffableDataSource?
  2. How to organise code so we don’t end up with a massive view controller?
  3. How to make an item selectable and refresh only those items that needs to be refreshed? (tapping on a day will show hourly forecast for that day)

1. How to handle heterogeneous items with UICollectionViewDiffableDataSource?

If you take a look at the documentation of UICollectionViewDiffableDataSource you’ll see its declaration is:

UICollectionViewDiffableDataSource type declaration

We’ll need sections and items, both conforming to Hashable protocol. For SectionIdentifierType we use this:

Sections for the collection view data source

The data is held together in a view model called WeatherViewModel , this contains all the information that defines a weather screen. We define a separate entity for Day and Hour that will be used to configure the cells. These are associated values in the Item enum cases, so the UICollectionViewDiffableDataSource can work with this type (as homogenous items).

The data source subclass is defined so for easier usage:

Subclassing UICollectionViewDiffableDataSource for convenience

The cell configuration happens in the block that is passed in the init function of the UICollectionViewDiffableDataSource , at this point we have the exact type information from the enum cases:

WeatherCollectionViewDataSource init method

2. How to organise code so we don’t end up with a massive view controller?

Regardless of the architecture you are using, it’s likely that you don’t want to assign the responsibility of managing the list’s content to the view controller. Hence we create a view model that encapsulates all the information that the view controller -> collection view -> cells need to render the screen.

As I mentioned before, the concrete Day and Hour types are wrapped in a value associated enum. We define a dictionary with Section as keys and theItem enum arrays as values. The title represents the location where we request the weather from:

How you propagate the view model to the view controller is up to you, however I show an example where a configure method is called on the view controller to pass on the view model.

Passing the view model to the view controller and reloading the content

3. How to make an item selectable and refresh only those items that have changed?

Selecting a different day should not cause a refresh for the cells of other days. It should refresh hourly breakdown cells, but only for the hours where the weather icon or temperature have changed.

At this point we need to introduce another entity to decouple the business logic (to select a day and show its hourly entries). Everyone uses their preferred architectural pattern, let’s go with a simple Presenter now as we use VIPER in Wise.

Let’s define two more entities calledDailyEntry and HourlyEntry, these represent the data we store (the Day and Hour we defined before are representing the view’s content — given the simplicity of the domain, these are almost identical). A unique id property is added, so we can identify which day is selected (and we add this id to the view related model as well, so on tapping we can associate the day model with the day view data model).

When the user taps on a day, the view tells the presenter to select the day

the presenter then loads the new day’s data (1.) (in the production app this will invoke a network request) and then configures the view with the new data (2.)

You can find the weather demo app’s code here

At Wise

We are working on a feature at Wise we call Insights. It enables users to have a better understanding of their spendings.

Insights feature in Wise is live

It’s the same concept as the weather app (what a coincidence), a collection view with heterogeneous items in it:

  • the months in the first section
  • a “legend” in the second
  • and the categories you spent your money on in the third.

Interested in solving problems like this? We’re hiring. Check out our open Engineering roles. (Feel free to message one of our tech recruiters on LinkedIn or sign up for job alerts on our careers site if you’re interested in joining us, but can’t find a suitable position).

Recap

  • To animate changes in the collection view’s data we can use a diffable datasource
  • If we have heterogenous (e.g. hourly weather entry / daily weather entry in 1./2. section) items, we can wrap them in a value associate enum
  • To minimise code in view controller and reduce its responsibility, encapsulate the data and inject it
  • To select an item we added an id property, that acts as a link between the view and data model
  • Demo project

Wise Engineering

Posts from the @Wise Engineering Team