Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionViewDiffableDataSource cellProvider called more often than expected

I'm using the UICollectionViewDiffableDataSource to populate my UICollectionView. After receiving a list of items via REST API, I create a new snapshot and apply it like this:

DispatchQueue.main.async {
   var snapshot = NSDiffableDataSourceSnapshot<RegionSection, DiffableModel>()
   snapshot.appendSections(RegionSection.allCases)
   snapshot.appendItems(self.spotlights, toSection: .Spotlights)
   snapshot.appendItems(self.vendors, toSection: .Vendors)
   self.dataSource?.apply(snapshot, animatingDifferences: animated)
}

When setting up my cells in the cellProvider, I asynchronously load images from a URL. I noticed, that the first cell would frantically flick through all the images that are loaded and end up displaying a different image than it was supposed to. (For example the image intended to be displayed by the last cell).

I decided to investigate and figured out that the cellProvider closure is called twice as many times as expected. Also the collectionView.dequeueReusableCell function behaves weirdly for the first half of the calls as it returns the same cell each time even though there are no cells in the collectionView that could be dequeued.

My cellProvider closure:

dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { (collectionView, indexPath, entry) -> UICollectionViewCell? in
    if let spotlight = entry as? Spotlight{
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "spotlightCell", for: indexPath) as! SpotlightCell
        
        cell.nameLabel.text = spotlight.title
        cell.subtitleLabel.text = spotlight.subtitle
        cell.categoryLabel.text = spotlight.type.getDescription().uppercased()
        cell.imageView.loadImage(fromUrl: spotlight.titlePictureUrl)
                    
        return cell
    }else if let vendor = entry as? Vendor{
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "vendorCell", for: indexPath) as! VendorCell
        
        cell.nameLabel.text = vendor.title
        cell.assortmentLabel.text = vendor.assortmentDescription
        cell.imageView.loadImage(fromUrl: vendor.titlePictureUrl ?? vendor.pictureUrls?.first ?? "")
        
        if let distance = vendor.distance{
            cell.distanceLabel.text = (distance/1000) < 1 ? (distance.getReadableString(withDecimalSeparator: ",", andDecimalCount: 0) + "m entfernt") : ((distance/1000).getReadableString(withDecimalSeparator: ",", andDecimalCount: 0) + "km entfernt")
        }
                    
        return cell
    }
    return nil
}

Here is an example:

  1. I create a snapshot containing 4 vendor entries (For simplicity I didn't add anything in the other section for this example)
  2. The cellProvider is called 4 times (for each indexPath and entry) and the cell that is dequeued is the same one each time.
  3. The cellProvider is called another 4 times (again, for each indexPath and entry) and this time the cells are different each time.
  4. For each time the cellProvider is invoked I call loadImage, which tries to find an image for the URL in my image cache and if it cannot found one loads it asynchronously.
  5. Since all calls happen almost simultaneously every image is loaded twice and the first cell displays one image after another until the last of the 4 URLSessions it initiated returns.

I can't imagine it is expected behaviour for the dataSource to call it's cellProvider closure that often and I simply can't figure out why this happens or find anything in the documentation on this.

I hope someone can explain to me why this happens and in case this is expected behaviour how to properly set up cells with asynchronous image loading using a DiffableDataSource.


EDIT: The solution that worked for me was to use absolute instead of estimated sizes for my cells, as suggested by @Norb Braun!

like image 201
Medwe Avatar asked Dec 03 '20 18:12

Medwe


Video Answer


1 Answers

Setting the estimated size to none fixed this issue for me. This solution may not work for you when you are required to use self sizing cells but if your cells keep the same size regardless the content you could give it a try.

like image 195
Norb Braun Avatar answered Nov 02 '22 20:11

Norb Braun