iOS 14 Tutorial: UICollectionView List | raywenderlich.com

In this tutorial, you’ll learn how to create lists, use modern cell configuration and configure multiple section snapshots on a single collection view.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/16906182-ios-14-tutorial-uicollectionview-list

Thanks for your remarkable explanation for this course.
By the way, I have a question about how to set adopted pet to its original available state. Can you give me some advice?

Hi! Yes, I can give you some advice. Here are some hints to help you going forward.

Create an “Abandon” button in the PetDetailViewController.

Handle a tap on this button via a delegate function, similar to the “Adopt” button delegate.

In the PetExplorerViewController, do the following when te “Abandon” button was tapped:.

  1. Remove the abandoned Pet from the adoptions.
  2. Delete the item of the abandoned Pet from the adoptedPetsSnapshot.items.

Thanks a lot for yours advice. I implemented some codes according to your advice and the app works as I expected.

Very nice and in-depth article!

1 Like

Is this tutorial part of any book or this is a standalone content. If it’s a part of a book, let me know which one, I’ll like to buy that.

Hi, thanks for your feedback. This tutorial is standalone content.

how to set margin between each cell?

Hi Peter, the tutorial is great, but it seems that the source code doesn’t work with XCode 13. It compiles but the app crashes :frowning:

The problem is that UICollectionView cells are being registered multiple times. In iOS 15 Apple has added an exception when this issue is detected.

To fix the crash you have to do the cell registration only once.

Fortunately this is easy to fix:

First move the functions for cell registration from the UICollectionView cells extension of PetExplorerViewController into the PetExplorerViewController class.

Then replace the functions by a constant.

  func categoryCellregistration() -> UICollectionView.CellRegistration<UICollectionViewListCell, Item> {
    ..
  }

becomes

  let categoryCellregistration: UICollectionView.CellRegistration<UICollectionViewListCell, Item> = {
    ..
  }()

Do the same for the other two cell registrations.

After applying these changes comment out the lines that do not compile (they create the backgroundConfiguration of the cell). I leave fixing this as a challenge :grinning:

Now the app won’t crash anymore.

From the docs for UICollectionView.CellRegistration:

Important

Do not create your cell registration inside a UICollectionViewDiffableDataSource.CellProvider closure; doing so prevents cell reuse, and generates an exception in iOS 15 and higher.

As you have indicated the solution in the previous comment, I think you should also update the tutorial accordingly (and explain why) rather than require searching for the answer.

Hey David, thanks for your feedback! I am not a member of the RW team anymore, so I can not update the article. That’s why I provide voluntary support via the comments now and then :slight_smile: .

I met the strange behavior after I adopted a pet. The cell’s background color of adopted pet had a colored background is correct. But others pets will have a colored background after I expanded.

In the original code, if the pet is adopted, the petCellRegistration function injects a cell.backgroundConfiguration with a color, etc.

This solution moves the adoption decision away from the cell registration function, and places it in the
dataSource factory function.

Modify the code as follows:

  func makePetCellRegistration() -> UICollectionView.CellRegistration<UICollectionViewListCell, Item> {
    return .init { cell, _, item in
      guard let pet = item.pet else {
        return
      }
      var configuration = cell.defaultContentConfiguration()
      configuration.text = pet.name
      configuration.secondaryText = "\(pet.age) years old"
      configuration.image = UIImage(named: pet.imageName)
      configuration.imageProperties.maximumSize = CGSize(width: 40, height: 40)
      cell.contentConfiguration = configuration
      
      cell.accessories = [.disclosureIndicator()]
    }
  }

Next, make a new cell registration with a modified background for a pet that is adopted:

  func makePetCellWithAdoptedBackgroundRegistration() -> UICollectionView.CellRegistration<UICollectionViewListCell, Item> {
    return .init { cell, _, item in
      guard let pet = item.pet else {
        return
      }

      var configuration = cell.defaultContentConfiguration()
      configuration.text = pet.name
      configuration.secondaryText = "\(pet.age) years old"
      configuration.image = UIImage(named: pet.imageName)
      configuration.imageProperties.maximumSize = CGSize(width: 40, height: 40)
      cell.contentConfiguration = configuration
      
      cell.accessories = [.disclosureIndicator()]
      
      // The pet *IS* adopted. Only adopted pets will have a colored background.
      var backgroundConfig = UIBackgroundConfiguration.listPlainCell()
      backgroundConfig.backgroundColor = .systemBlue
      backgroundConfig.cornerRadius = 5
      backgroundConfig.backgroundInsets = NSDirectionalEdgeInsets(
        top: 5, leading: 5, bottom: 5, trailing: 5)
      cell.backgroundConfiguration = backgroundConfig
    }
  }

Finally, modify makeDataSource to check if the pet is adopted before assigning a cell registration:

  func makeDataSource() -> DataSource {

    let categoryCellRegistration = makeCategoryCellRegistration()
    let petCellRegistration = makePetCellRegistration()
    let petCellWithAdoptedBackgroundRegistration = makePetCellWithAdoptedBackgroundRegistration()
    let adoptedPetCellRegistration = makeAdoptedPetCellRegistration()
        
    return DataSource(collectionView: collectionView) {
      collectionView, indexPath, item -> UICollectionViewCell? in
      
      if let pet = item.pet {
        guard let section = Section(rawValue: indexPath.section) else {
          return nil
        }
        switch section {
          case .availablePets:
            if self.adoptions.contains(pet) {
              return collectionView.dequeueConfiguredReusableCell(
                using: petCellWithAdoptedBackgroundRegistration, for: indexPath, item: item)
            }
            else {
              return collectionView.dequeueConfiguredReusableCell(
                using: petCellRegistration, for: indexPath, item: item)
            }

        case .adoptedPets:
          return collectionView.dequeueConfiguredReusableCell(
            using: adoptedPetCellRegistration, for: indexPath, item: item)
        }
      }
      else {
        return collectionView.dequeueConfiguredReusableCell(
          using: categoryCellRegistration, for: indexPath, item: item)
      }
    }
  }