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 3 of 3 of this article. Click here to view the first page.

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.

The videos are separated into sections

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:

  1. You add a class and make it a subclass of UICollectionReusableView. This means the section header view can be reused just like the cells.
  2. You setup the title label’s style
  3. 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:

  1. Get an instance of the section for the supplementary view.
  2. Ensure the supplementary view provider asks for a header.
  3. Dequeue a new header view.
  4. Retrieve the section from the data source, then set the titleLabel‘s text value to the section‘s title.

Build and run.

Final build and run screenshot with section headers.

And, here’s how the app looks on an iPad:

The sections now have titles

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!