Sample Code

Prefetching Collection View Data

Load data for collection view cells before they are displayed.

Download

Overview

A collection view displays an ordered collection of cells in customizable layouts. The UICollectionViewDataSourcePrefetching protocol helps provide a smoother user experience by prefetching the data necessary for upcoming collection view cells. When you enable prefetching, the collection view requests the data before it needs to display the cell. When it’s time to display the cell, the data is already locally cached.

The image below shows cells outside the bounds of the collection view that have been prefetched.

CollectionViewPrefetching.app

Enable Prefetching

The root view controller uses an instance of the CustomDataSource class to provide data to its UICollectionView instance. The CustomDataSource class implements the UICollectionViewDataSourcePrefetching protocol to begin fetching the data required to populate cells.

class CustomDataSource: NSObject, UICollectionViewDataSource, UICollectionViewDataSourcePrefetching {

In addition to assigning the CustomDataSource instance to the collection view’s dataSource property, you must also assign it to the prefetchDataSource property.

// Set the collection view's data source.
collectionView.dataSource = dataSource

// Set the collection view's prefetching data source.
collectionView.prefetchDataSource = dataSource

Load Data Asynchronously

You use data prefetching when loading data is a slow or expensive process—for example, when fetching data over the network. In these circumstances, perform data loading asynchronously. In this sample, the AsyncFetcher class is used to fetch data asynchronously, simulating a network request.

First, implement the UICollectionViewDataSourcePrefetching prefetch method, invoking the appropriate method on the async fetcher:

func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
    // Begin asynchronously fetching data for the requested index paths.
    for indexPath in indexPaths {
        let model = models[indexPath.row]
        asyncFetcher.fetchAsync(model.id)
    }
}

When prefetching is complete, the cell’s data is added to the AsyncFetcher‘s cache, so it’s ready to be used when the cell is displayed. The cell’s background color changes from white to red when data is available for that cell.

/**
 Configures the cell for display based on the model.
 
 - Parameters:
     - data: An optional `DisplayData` object to display.
 
 - Tag: Cell_Config
*/
func configure(with data: DisplayData?) {
    backgroundColor = data?.color
}

Populate Cells for Display

Before populating a cell, the CustomDataSource first checks for any prefetched data that it can use. If none is available, the CustomDataSource makes a fetch request and the cell is updated in the fetch request’s completion handler.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Cell.reuseIdentifier, for: indexPath) as? Cell else {
        fatalError("Expected `\(Cell.self)` type for reuseIdentifier \(Cell.reuseIdentifier). Check the configuration in Main.storyboard.")
    }
    
    let model = models[indexPath.row]
    let id = model.id
    cell.representedId = id
    
    // Check if the `asyncFetcher` has already fetched data for the specified identifier.
    if let fetchedData = asyncFetcher.fetchedData(for: id) {
        // The data has already been fetched and cached; use it to configure the cell.
        cell.configure(with: fetchedData)
    } else {
        // There is no data available; clear the cell until we've fetched data.
        cell.configure(with: nil)

        // Ask the `asyncFetcher` to fetch data for the specified identifier.
        asyncFetcher.fetchAsync(id) { fetchedData in
            DispatchQueue.main.async {
                /*
                 The `asyncFetcher` has fetched data for the identifier. Before
                 updating the cell, check if it has been recycled by the
                 collection view to represent other data.
                 */
                guard cell.representedId == id else { return }
                
                // Configure the cell with the fetched image.
                cell.configure(with: fetchedData)
            }
        }
    }

    return cell
}

Cancel Unnecessary Fetches

Implement the collectionView(_:cancelPrefetchingForItemsAt:) delegate method to cancel any in-progress data fetches that are no longer required. An example of how to handle this is taken from the sample and shown below.

func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
    // Cancel any in-flight requests for data for the specified index paths.
    for indexPath in indexPaths {
        let model = models[indexPath.row]
        asyncFetcher.cancelFetch(model.id)
    }
}

See Also

Managing Data Prefetching

func collectionView(UICollectionView, prefetchItemsAt: [IndexPath])

Instructs your prefetch data source object to begin preparing data for the cells at the supplied index paths.

Required.

func collectionView(UICollectionView, cancelPrefetchingForItemsAt: [IndexPath])

Cancels a previously triggered data prefetch request.