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.

4.9 (37) · 3 Reviews

Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

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:

  1. 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.
  2. Create a new Snapshot object.
  3. Add the .main section to the snapshot. This is the only section type you currently have defined for your application.
  4. Add the video array to the snapshot.
  5. 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.

The RayTube app with multiple videos on screen

It works! But there is a small problem. Search for something, and you’ll notice the user interface doesn’t update at all.

The user types text into the search field, but the UI doesn't update

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:

The user types text into the search field, and the UI animates to reflect their search query

If you run the app on an iPad, the animation is even more complex, all for free!

The user types text into the iPad search field, and the UI animates to reflect their search query

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:

  1. Like the Video class, you conform Section to Hashable.
  2. Section has two important properties that you’ll use in a moment to categorize the videos: title and videos.

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.