iOS Tutorial: Collection View and Diffable Data Source
In this iOS tutorial, you’ll learn how to implement a collection view with UICollectionViewDiffableDataSource and NSDiffableDataSourceSnapshot. By Jordan Osterberg.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
iOS Tutorial: Collection View and Diffable Data Source
20 mins
- Getting Started
- What is UICollectionViewDiffableDataSource?
- Benefits of UICollectionViewDiffableDataSource
- Creating a Diffable Data Source
- Implementing Hashable
- Configuring The Diffable Data Source
- Using NSDiffableDataSourceSnapshot
- Fixing Search
- Multiple Sections
- Option One
- Creating the Section Class
- Adopting the New Section Class
- Fixing Search, Again
- Supplementary Views
- Where to Go From Here?
Using NSDiffableDataSourceSnapshot
NSDiffableDataSourceSnapshot
stores your sections and items, which the diffable data source references to understand how many sections and cells to display.
Just like you created a type alias for UICollectionViewDiffableDataSource
, you can create a Snapshot
type alias as well.
Add the following type alias to VideosViewController
:
typealias Snapshot = NSDiffableDataSourceSnapshot<Section, Video>
NSDiffableDataSourceSnapshot
, like UICollectionViewDiffableDataSource
, takes a section type and an item type: Section
and Video
.
Now, it’s time to create a snapshot!
Below viewDidLoad()
, add the following method:
// 1
func applySnapshot(animatingDifferences: Bool = true) {
// 2
var snapshot = Snapshot()
// 3
snapshot.appendSections([.main])
// 4
snapshot.appendItems(videoList)
// 5
dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
}
With applySnapshot(animatingDifferences:)
you:
- Create a new method that applies a snapshot to the data source. The method takes a Boolean which determines if changes to the data source should animate.
- Create a new
Snapshot
object. - Add the
.main
section to the snapshot. This is the only section type you currently have defined for your application. - Add the video array to the snapshot.
- Tell the
dataSource
about the latest snapshot so it can update and animate accordingly.
Great! Now call the method at the end of viewDidLoad()
:
applySnapshot(animatingDifferences: false)
Build and run.
It works! But there is a small problem. Search for something, and you’ll notice the user interface doesn’t update at all.
Fortunately, fixing the search feature is super easy!
Fixing Search
Inside updateSearchResults(for:)
, replace:
collectionView.reloadData()
With:
applySnapshot()
Instead of reloading the entire collection view, you new apply a new snapshot to the database, which will cause the changes to animate.
Build and run. Type No in the search bar and watch the UI animate:
If you run the app on an iPad, the animation is even more complex, all for free!
Success! Next, you’re going to learn how to implement multiple sections.
Multiple Sections
There are two ways you can implement multiple sections using the diffable data source API.
Option One
Remember the Section
enum?
[spoiler title=”The Section
enum”]
enum Section {
case main
}
[/spoiler]
You can add another case to the enum to implement multiple sections. This option is great when you have a predefined set of sections you’d like to display. For example, a messaging app with a friends section and an others section.
However, if you have no easy way of knowing what sections you’d like your app to display, option two is for you!
Option Two
The second option is to change Section
from a value type to a class. Afterward, you can freely create any number of these objects without having to predefine each section.
This option is great if you have a server that provides categories that can change at any time, or if you allow users to create sections dynamically.
Because the RayTube app has more than a few sections, and because these sections may change later if your data changes, you’ll go with this option.
Creating the Section Class
Inside VideosViewController.swift, remove the following code:
enum Section {
case main
}
Next, create a new file named Section.swift. Add the following code to the file:
import UIKit
// 1
class Section: Hashable {
var id = UUID()
// 2
var title: String
var videos: [Video]
init(title: String, videos: [Video]) {
self.title = title
self.videos = videos
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
static func == (lhs: Section, rhs: Section) -> Bool {
lhs.id == rhs.id
}
}
Here’s what you did:
- Like the
Video
class, you conformSection
toHashable
. -
Section
has two important properties that you’ll use in a moment to categorize the videos:title
andvideos
.
Next, you need to create some sections. Open Video.swift and scroll down. You’ll notice an extension on the class with a static property named allVideos
. This array stores all the videos the app displays.
Now that you’re introducing multiple sections, this property is insufficient. Remove all the code below // MARK: - Video Sample Data
.
This will cause an issue in the places where you use Video.allVideos
, but don’t worry about that for now. You’ll fix that in just a moment.
Next, open Section.swift. Then paste the following code at the bottom of the file:
extension Section {
static var allSections: [Section] = [
Section(title: "SwiftUI", videos: [
Video(
title: "SwiftUI",
thumbnail: UIImage(named: "swiftui"),
lessonCount: 37,
link: URL(string: "https://www.raywenderlich.com/4001741-swiftui")
)
]),
Section(title: "UIKit", videos: [
Video(
title: "Demystifying Views in iOS",
thumbnail: UIImage(named: "views"),
lessonCount: 26,
link:
URL(string:
"https://www.raywenderlich.com/4518-demystifying-views-in-ios")
),
Video(
title: "Reproducing Popular iOS Controls",
thumbnail: UIImage(named: "controls"),
lessonCount: 31,
link: URL(string: """
https://www.raywenderlich.com/5298-reproducing
-popular-ios-controls
""")
)
]),
Section(title: "Frameworks", videos: [
Video(
title: "Fastlane for iOS",
thumbnail: UIImage(named: "fastlane"),
lessonCount: 44,
link: URL(string:
"https://www.raywenderlich.com/1259223-fastlane-for-ios")
),
Video(
title: "Beginning RxSwift",
thumbnail: UIImage(named: "rxswift"),
lessonCount: 39,
link: URL(string:
"https://www.raywenderlich.com/4743-beginning-rxswift")
)
]),
Section(title: "Miscellaneous", videos: [
Video(
title: "Data Structures & Algorithms in Swift",
thumbnail: UIImage(named: "datastructures"),
lessonCount: 29,
link: URL(string: """
https://www.raywenderlich.com/977854-data-structures
-algorithms-in-swift
""")
),
Video(
title: "Beginning ARKit",
thumbnail: UIImage(named: "arkit"),
lessonCount: 46,
link: URL(string:
"https://www.raywenderlich.com/737368-beginning-arkit")
),
Video(
title: "Machine Learning in iOS",
thumbnail: UIImage(named: "machinelearning"),
lessonCount: 15,
link: URL(string: """
https://www.raywenderlich.com/1320561-machine-learning-in-ios
""")
),
Video(
title: "Push Notifications",
thumbnail: UIImage(named: "notifications"),
lessonCount: 33,
link: URL(string:
"https://www.raywenderlich.com/1258151-push-notifications")
),
])
]
}
Phew, lots of code. Here you created a static property allSections
which has four sections with one or more videos each. This is basically just dummy data — in a fully fledged application you would fetch this information from a server.
With this, you can now access the app’s sections using the Section.allSections
property.
Adopting the New Section Class
Head back to VideosViewController.swift.
Replace:
private var videoList = Video.allVideos
…with:
private var sections = Section.allSections
Next, you need to update applySnapshot(animatingDifferences:)
to work with Section
.
Replace the following code in applySnapshot(animatingDifferences:)
:
snapshot.appendSections([.main])
snapshot.appendItems(videos)
…with:
snapshot.appendSections(sections)
sections.forEach { section in
snapshot.appendItems(section.videos, toSection: section)
}
There are two changes here. First, you append the sections
array to the snapshot. Second, you loop over each section and add its items (videos) to the snapshot.
You also specify each video’s section explicitly, because the data source won’t infer the item’s section correctly now that there are multiple sections.