There’s a common requirement for modern iOS apps to display data in lists and grids of all kinds, with animations in response to the changes in that data due to user actions and/or network calls.
These days, SwiftUI framework makes it very easy to display such dynamic lists and grids with smooth animations and custom behaviors. However, as sleek and as expressive SwiftUI may be, the reality is that there are still far too many iOS apps that rely on UIKit. This is understandable, as for some projects the costs of migration to SwiftUI may far exceed the gains.
That being said, something as simple as displaying a list of dynamic data in UIKit can be a pretty awkward ordeal. Back in the day, iOS developers had to do a lot of manual setup to make sure that the data is updated correctly in the view, which made the whole process error prone. Fortunately, Apple has addressed these issues with the introduction of their new diffable data source—a modern way to feed data to tables and collections in UIKit.
Don’t let the weird terminology scare you—in practice, the diffable data source is a lot easier to use than anything that came before it in UIKit, and is arguably the best way to manage data in table and collection views. In this article I’ll guide you through the setup of the diffable data source in UIKit.

Want to see this code in action?
I created a sample Xcode project on Gihub that demonstrates the implementation discussed in this article. Feel free to check it out and use in your projects.
What’s Diffable Data Source
In UIKit, the concept of a data source refers to an object that acts as an intermediary between your data and the table or collection view that displays it. This object manages data updates within the view and provides the cells filled with content that populate it.
When we talk about diffable data source in particular, the term “diffable” means that this data source operates by taking snapshots of the data and comparing the differences between those snapshots to apply changes to the view accordingly.
There are three main types to work with when using a diffable data source in UIKit:
UITableViewDiffableDataSource– As the name suggests, this data source is used in conjunction withUITableView;UICollectionViewDiffableDataSource– This data source is used to fill aUICollectionViewwith data.NSDiffableDataSourceSnapshot— This type represents a snapshot of the data, which can be applied to both data sources mentioned above.
Both of the diffable data source types work essentially in the same way, so once you’ve learned one, you’ll have no trouble implementing the other.
In this article, I’ll be using the UICollectionView and UICollectionViewDiffableDataSource for my examples, because the UICollectionView type allows to implement any kind of interface, including default system lists analogous to the ones the UITableView provides. However, if you’d like to learn how to use UITableView and it’s corresponding UITableViewDiffableDataSource type in particular, then check out my other article that covers this topic specifically.
The setup of diffable data source generally involves the following steps:
- Define diffable data source
- Configure layout (relevant for the
UICollectionViewonly) - Register reusable cells
- Implement cell provider
- Connect diffable data source to view
- Create and apply snapshot
Once this setup is complete, you simply repeat the Step VI whenever you need to update the data in the view. Let’s go over these steps in more detail.
UICollectionViewDiffableDataSource in action—effortlessly handling updates with smooth animations. Check out the code for this video on my GitHub.Note About My Approach
In my code, I aim to encapsulate different parts of functionality to make the implementation easy to remember and understand. While many of the functions in this article could be implemented as trailing closures, I prioritize separation of concerns. In this way I’m not only keeping this code cleaner but, more importantly, making this guide more comprehensive for you—the reader.
I. Define Diffable Data Source
As mentioned above, the diffable data source uses a notion of snapshots that represent the state of the data. Both—diffable data source and its snapshots—are generic types that take two type parameters to identify sections and items. Sections are used to group items in a meaningful way, while the items embody the cells of the collection view.

You can use any type to identify your sections and items, as long as the type conforms to Hashable protocol. It can be something as simple as Int for sections and UUID for items, or you can use your own custom types. The conformance to Hashable is necessary for the diffable data source to keep track of changes between the snapshots.
Defining a diffable data source, in this case, means parameterizing it with the types you’ll be using to identify your sections and items.
Define Sections
For simple cases it’s perfectly fine to use the Int type to represent sections, especially if your collection has only one section. However, I find it useful to explicitly define the sections for clarity.
Enumerations are great for defining sections, especially considering that enum’s are Hashable by default in Swift.
// Example with a single section
enum Section {
case main
}
// Example with multiple sections
enum Section {
case books, movies
var title: String {
switch self {
case .books: return “My favorite books"
case .movies: return “My favorite movies"
}
}
}
Define Items
Item identifiers can be expressed with enumerations as well. Using associated values you can pass any kind of data for an item as long as the associated value conforms to Hashable protocol. If you do use the associated values however, you have to explicitly adopt the Hashable protocol on your enumeration.
enum Item: Hashable {
case sectionHeader(title: String)
case book(title: String, pages: Int)
case movie(title: String, length: Double)
case emptyState
}
The cases in the enumeration can allow you to treat items in collections differently providing relevant views for each case.
You can also use custom data types as item identifiers in a diffable data source by conforming them to the Hashable protocol, which gives you direct access to the underlying data. To make a struct conform to Hashable, ensure that all stored properties are Hashable, or implement the hash(into:) method. For most cases, hashing a UUID will suffice.
struct Movie: Identifiable {
let id: UUID = UUID()
var title: String
var length: Double
}
extension Movie: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
Define Diffable Data Source and Snapshot
Once you’ve decided on the section and item types, use them to parameterize your diffable data source by writing the types inside the angle brackets.
var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
Declaring the data source at the top of the view controller as an explicitly unwrapped optional allows to skip pesky initializer implementation and rely on the familiar viewDidLoad method instead.
Tip: Use Type Aliases
Another way to parameterize the diffable data source is to use type aliases to handle the unwieldy type names, making the code easier to read and reason about.
// Diffable data source for collection view
typealias DataSource = UICollectionViewDiffableDataSource<Section, Item>
// Snapshot for diffable data source
typealias Snapshot = NSDiffableDataSourceSnapshot<Section, Item>
II. Configure Collection View Layout
The cool thing about the UICollectionView is that it supports virtually any kind of layout—grids, lists, or custom compositions. The layouts for collection views are created with the UICollectionViewLayout subclasses, thus the implementation varies significantly between different layouts.
For simplicity, this article covers implementing a list layout using the UICollectionLayoutListConfiguration type, as it’s the fastest, easiest way to set up a collection view layout—and likely the most commonly needed. To learn how to implement grid layouts for UICollectionView check out my article How to Create Grid Layout in UIKit.
Configure List Configuration
To create a list layout for the UICollectionView use the list(using:) static method on UICollectionViewCompositionalLayout class. This method takes one argument of the UICollectionLayoutListConfiguration type that you use to configure list properties such as whether to show separators, header mode, swipe gestures, etc.
Apply the generated list layout to your UICollectionView by assigning it to the colectionViewLayout property of the collection view.
func configureCollectionViewLayout() {
// 1. Create configuration
var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
// 2. Configure properties
configuration.showsSeparators = true
// 3. Use configuration to create layout
let layout = UICollectionViewCompositionalLayout.list(using: configuration)
// 4. Apply layout to collection view
collectionView.collectionViewLayout = layout
}
Note: Animated Layout Change
You can also use the setCollectionViewLayout(_:animated:) method on your UICollectionView to set the layout with animation if necessary.
Section Headers Made Easy
When you set the headerMode property of the UICollectionLayoutListConfiguration to firstItemInSection, the configuration uses the first item in each section as the section header. This feature is extremely useful, as it lets you avoid separate registration for header views by creating headers with dedicated item identifiers instead.
configuration.headerMode = .firstItemInSection
Effortless Swipe Gestures
With the UICollectionLayoutListConfiguration it’s very easy to create swipe gestures for the list items. You simply provide a closure to any of its trailingSwipeActionsConfigurationProvider or leadingSwipeActionsConfigurationProvider properties that takes the IndexPath of the item that was swiped, and returns the UISwipeActionsConfiguration. Use the IndexPath to identify the swiped item and provide relevant UIContextualAction swipe actions. To apply the swipe actions to an item simply return the swipe actions configuration as the UISwipeActionsConfiguration type instantiated with an array of your UIContextualAction swipe actions.
configuration.trailingSwipeActionsConfigurationProvider = { indexPath in
// Ensure not to swipe first items in sections as those are used as section headers
guard indexPath.item != 0 else { return nil }
// Create swipe actions
let action = UIContextualAction(style: .normal, title: "Okay") { action, view, completion in
print("Item swiped!")
completion(true)
}
// Return swipe configuration with actions
return UISwipeActionsConfiguration(actions: [action])
}
III. Register Reusable Cells
For optimal performance the UICollectionView uses a mechanism for reusing cells. Without delving into the nitty-gritty of how this works, the main point is that, prior to connecting the data source to the view, you must register your reusable cells.
There are multiple ways to register cells for a collection view, but I find the use of UICollectionView.CellRegistration type to be by far the most straightforward way, as it does away with unnecessary String reuse identifiers.
Parameterize Cell Registration
The UICollectionView.CellRegistration is a generic type and you need to parameterize with the type of the view for cells and the item type in your diffable data source.
var cellRegistration: UICollectionView.CellRegistration<UICollectionViewListCell, Item>!
Be sure to use UICollectionViewListCell as your cell type when creating list collections to take advantage of all the default system features for list items.
Implement Cell Registration Handler
To create the UICollectionView.CellRegistration you need to pass in a closure that configures the individual cells, known as the cell registration handler. This handler takes three arguments:
cell– the cell to configure. Use this parameter to retrieve default list configuration for cells.indexPath— the location of the cell in the collection view.itemIdentifier— the item type in your diffable data source. Use it to access the data to populate the cell.
func cellRegistrationHandler(for cell: UICollectionViewListCell, at indexPath: IndexPath, item: Item) {
// 1. Retrieve default list configuration
var contentConfiguration = cell.defaultContentConfiguration()
// 2. Apply content
switch item {
case .sectionHeader(let title): // dedicated item for section headers
contentConfiguration.text = title
case .book(let title, let pages):
contentConfiguration.text = title
contentConfiguration.secondaryText = "\(pages) pages"
case .movie(let title, let length):
contentConfiguration.text = title
contentConfiguration.secondaryText = "\(length) hours"
default:
break
}
// 3. Apply styles
contentConfiguration.secondaryTextProperties.color = .secondaryLabel
// 4. Set configuration
cell.contentConfiguration = contentConfiguration
// 5. Optional: add accessories.
cell.accessories = [.disclosureIndicator()]
}
Separating cell registration and its handler in this way results in clearer code, easier maintenance, and better separation of concerns, making it more future-proof.
Register Cells
Finally, initialize the cell registration object before connecting data source to the view by passing in the registration handler closure defined above.
func registerCells() {
cellRegistration = .init(handler: cellRegistrationHandler)
}
IV. Implement Cell Provider
In context of diffable data source a cell provider is a closure that configures content and appearance of individual cells. This closure takes three arguments:
collectionView— TheUICollectionViewthat data source is connected to. Used it to dequeue the cells with cell registration declared in previous step.indexPath— The location, if you will, of the item within the collection view.itemIdentifier— The item type used in data source.
Use these parameters to return the cell of the type that you need, for example for a list layout the return type will be UICollectionViewListCell.
Since we’re using UICollectionView.CellRegistration to register our cells, the implementation of the cell provider ends up being just one line of code.
func cellProvider(for collectionView: UICollectionView, at indexPath: IndexPath, item: Item) -> UICollectionViewCell {
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
}
V. Connect Data Source to View
Diffable data source connects to collection view during initialization, so you don’t need to directly assign it to the dataSource property on your UICollectionView. To create diffable data source use its init(collectionView:cellProvider) initializer passing in the collection view you want to connect to and the cell provider defined in previous step.
func connectDataSource() {
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView, cellProvider: cellProvider)
}
VI. Create and Apply Snapshot
As mentioned earlier, a diffable data source uses snapshots represented with the NSDiffableDataSourceSnapshot type to fill the view with data. Whenever a new snapshot is applied, the diffable data source updates the view it’s connected to.
Typical workflow with snapshots adheres to the following pattern:
- Create a snapshot;
- Make changes to the snapshot by manipulating the sections and items;
- Apply the snapshot to data source.
func applySnapshot() {
// 1. Create empty snapshot
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
// 2. Make changes to snapshot
// Append sections
snapshot.appendSections([.books, .movies])
// These first items are section headers as per UICollectionLayoutListConfiguration.headerMode = .firstItemInSection
snapshot.appendItems([.sectionHeader(title: Section.books.title)], toSection: .books)
snapshot.appendItems([.sectionHeader(title: Section.movies.title)], toSection: .movies)
// The rest of the items
snapshot.appendItems(Item.sampleData.books, toSection: .books)
snapshot.appendItems(Item.sampleData.movies, toSection: .movies)
// 3. Apply the snapshot
dataSource.apply(snapshot)
}

The NSDiffableDataSourceSnapshot provides many useful methods to work with sections and items. For example, you can delete items by retrieving the current snapshot and by calling the deleteItems(_:) method on it.
func delete(item: Item) {
// 1. Retrieve current snapshot
var updatedSnapshot = dataSource.snapshot()
// 2. Make changes to snapshot
// In this case, delete the item
updatedSnapshot.deleteItems([item])
// 3. Apply updated snapshot
dataSource.apply(updatedSnapshot, animatingDifferences: true)
}
Passing true in the apply(_:animatingDifferences:) call will animate the updates in the view.
What’s next
Now that all of the functionality is ready, all that is left to do is to call the functions we declared above at the right time in the viewDidLoad.
func viewDidLoad() {
super.viewDidLoad()
configureCollectionViewLayout()
registerCells()
connectDataSource()
applySnapshot()
}
Then, whenever you need to display changes in data, simply repeat pattern discussed in the previous step: create a snapshot, make changes to it, apply it.
Custom Content Configurations
The default list configuration discussed in this guide will fit majority of simple use cases, but sometimes there’s need to display more complex views in your cells, such as text fields in forms, or specialized views such as maps.

UIContentView protocol you can implement any content view for your cells.To satisfy this need you can create custom UIContentView with dedicated configurations, but that’s a huge topic on its own, which I covered in my other guide called How to Use UIContentView in UIKit.
Wrap Up
Well, there you have it. As you can see, the UIKit is still awkward, but the diffable data source does make working with tables and collections much easier. If you’d like to see how UIKit compares to SwiftUI in implementation of tables and collections, then I encourage you to check out my sample project on GitHub that I created for this article.
For more information on this topic I suggest to check out the WWDC19 session Advances in UI Data Sources that introduces the diffable data source and the official Apple tutorial Getting started with Today that touches on this topic as well.
I hope that with this guide succeeded in demystifying the diffable data sources for you, and provided you with some useful insights. Happy diffing!

