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?
Fixing Search, Again
Now that you have sections in the app, you need to fix the search feature again. This is the last time, I promise. The previous method that processed a search query returned an array of videos, so you need to write a new method that returns an array of sections.
Replace filteredVideos(for:)
with the following method:
func filteredSections(for queryOrNil: String?) -> [Section] {
let sections = Section.allSections
guard
let query = queryOrNil,
!query.isEmpty
else {
return sections
}
return sections.filter { section in
var matches = section.title.lowercased().contains(query.lowercased())
for video in section.videos {
if video.title.lowercased().contains(query.lowercased()) {
matches = true
break
}
}
return matches
}
}
This new filter returns all sections whose name matches the search criteria plus those that contain a video whose title matches the search.
Inside updateSearchResults(for:)
, replace:
videoList = filteredVideos(for: searchController.searchBar.text)
…with:
sections = filteredSections(for: searchController.searchBar.text)
This switches out the search filter for the new section-based version you just implemented.
Phew! That was quite an adventure.
You can see the sections better on an iPad, so use an iPad simulator. Build and run.
Awesome! The videos are categorized! But, there isn’t an easy way to see what category a video is a part of.
Supplementary Views
To add a header to the sections, you need to implement a supplementary header view. Don’t worry because this isn’t as complicated as it sounds.
First, create a new file named SectionHeaderReusableView.swift. Add the following code to the file:
import UIKit
// 1
class SectionHeaderReusableView: UICollectionReusableView {
static var reuseIdentifier: String {
return String(describing: SectionHeaderReusableView.self)
}
// 2
lazy var titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(
ofSize: UIFont.preferredFont(forTextStyle: .title1).pointSize,
weight: .bold)
label.adjustsFontForContentSizeCategory = true
label.textColor = .label
label.textAlignment = .left
label.numberOfLines = 1
label.setContentCompressionResistancePriority(
.defaultHigh,
for: .horizontal)
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
// 3
backgroundColor = .systemBackground
addSubview(titleLabel)
if UIDevice.current.userInterfaceIdiom == .pad {
NSLayoutConstraint.activate([
titleLabel.leadingAnchor.constraint(
equalTo: leadingAnchor,
constant: 5),
titleLabel.trailingAnchor.constraint(
lessThanOrEqualTo: trailingAnchor,
constant: -5)])
} else {
NSLayoutConstraint.activate([
titleLabel.leadingAnchor.constraint(
equalTo: readableContentGuide.leadingAnchor),
titleLabel.trailingAnchor.constraint(
lessThanOrEqualTo: readableContentGuide.trailingAnchor)
])
}
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(
equalTo: topAnchor,
constant: 10),
titleLabel.bottomAnchor.constraint(
equalTo: bottomAnchor,
constant: -10)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is quite the block of code, but there’s not much to it. In short, the view has one label which displays the title of the section. Going over the code:
- You add a class and make it a subclass of
UICollectionReusableView
. This means the section header view can be reused just like the cells. - You setup the title label’s style
- On initialization, you add the title label to the header view and set up its Auto Layout constraints. Depending on whether you’re on an iPad or not you use different rules.
Open VideosViewController.swift. Below // MARK: - Layout Handling
, add the following code to the beginning of configureLayout()
:
collectionView.register(
SectionHeaderReusableView.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: SectionHeaderReusableView.reuseIdentifier
)
This registers the header view you just wrote with the collection view, so you can use section headers.
Next, in the same method, add the following code inside the sectionProvider
closure and right before return section
:
// Supplementary header view setup
let headerFooterSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(20)
)
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: headerFooterSize,
elementKind: UICollectionView.elementKindSectionHeader,
alignment: .top
)
section.boundarySupplementaryItems = [sectionHeader]
This code tells the layout system that you’d like to display a header for every section.
You’re almost done! Inside makeDataSource()
, add the following code right before return dataSource
:
// 1
dataSource.supplementaryViewProvider = { collectionView, kind, indexPath in
// 2
guard kind == UICollectionView.elementKindSectionHeader else {
return nil
}
// 3
let view = collectionView.dequeueReusableSupplementaryView(
ofKind: kind,
withReuseIdentifier: SectionHeaderReusableView.reuseIdentifier,
for: indexPath) as? SectionHeaderReusableView
// 4
let section = self.dataSource.snapshot()
.sectionIdentifiers[indexPath.section]
view?.titleLabel.text = section.title
return view
}
Here you:
- Get an instance of the section for the supplementary view.
- Ensure the supplementary view provider asks for a header.
- Dequeue a new header view.
- Retrieve the section from the data source, then set the
titleLabel
‘s text value to thesection
‘s title.
Build and run.
And, here’s how the app looks on an iPad:
Success!
Where to Go From Here?
Great job getting this far! You can download the final project by using the Download Materials button at the top or bottom of this page.
In this tutorial, you’ve learned how to add UICollectionViewDiffableDataSource
to your existing collection view-based project.
If you want a challenge, try to introduce different collection view cells based on the item returned by the data source.
The sample app also uses compositional layouts. If you’d like to learn more about them, check out Modern Collection Views with Compositional Layouts.
If you have any questions or comments, join the forum below!