'UICollectionView doesn't configure nor prefetch the second item until scrolling

I use UICollectionView to present cells. Each cell takes up the full screen size. The collection view is created with several items while only the first one is configured and displayed on the screen.

The problem is that the second item is not configured nor prefetched unless collectionView is scrolled down.

In my use case cell configuration fetches data from remote server, which I prefer doing the sooner the better. When I scroll down but the second cell isn't configured there is nothing to present yet.

I suspected that the layout has something to do with it, so I tried to use UICollectionViewFlowLayout as well as UICollectionViewCompositionalLayout and in both cases the problem occurs.

Is it possible to force the collection view to call the configure method of the second cell earlier?

I created a demo swift project with collectionView presenting screen sized colored rectangles. Cell configuration and prefetch is logged to the console with their indexPath.

import Foundation
import UIKit

enum Section {
  case main
}

typealias DataSource = UICollectionViewDiffableDataSource<Section, UIColor>

typealias Snapshot = NSDiffableDataSourceSnapshot<Section, UIColor>

class CollectionViewController: UIViewController, UICollectionViewDelegateFlowLayout,
                                    UICollectionViewDataSourcePrefetching {
  private lazy var dataSource: DataSource = makeDataSource()

  private let collectionView: UICollectionView

  private static let cellIdentifier = "CollectionViewCell"

  init() {
//    let layout = Self.makeFlowLayout()
    let layout = Self.makeCompositionLayout()

    print("Log: collectionview layout - \(layout.description)")
    collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
    collectionView.register(
      UICollectionViewCell.self,
      forCellWithReuseIdentifier: Self.cellIdentifier
    )

    super.init(nibName: nil, bundle: nil)
  }

  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override func viewDidLoad() {
    super.viewDidLoad()

    setupCollectionView()
    applySnapshot(animatingDifferences: false)
  }

  private static func makeFlowLayout() -> UICollectionViewFlowLayout {
    let flowLayout = UICollectionViewFlowLayout()
    flowLayout.scrollDirection = .vertical
    flowLayout.minimumLineSpacing = 0
    return flowLayout
  }

  private static func makeCompositionLayout() -> UICollectionViewLayout {
    let config = UICollectionViewCompositionalLayoutConfiguration()
    config.interSectionSpacing = 0

    let fullSize = NSCollectionLayoutSize(
      widthDimension: .fractionalWidth(1),
      heightDimension: .fractionalHeight(1)
    )
    let item = NSCollectionLayoutItem(layoutSize: fullSize)
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: fullSize, subitems: [item])
    let layout = NSCollectionLayoutSection(group: group)

    return UICollectionViewCompositionalLayout(section: layout, configuration: config)
  }

  private func setupCollectionView() {
    collectionView.backgroundColor = .white
    collectionView.delegate = self
    collectionView.prefetchDataSource = self
    collectionView.isPrefetchingEnabled = true

    collectionView.contentInsetAdjustmentBehavior = .never

    view.addSubview(collectionView)

    collectionView.translatesAutoresizingMaskIntoConstraints = false
    let contraints = [
      collectionView.leftAnchor.constraint(equalTo: view.leftAnchor),
      collectionView.rightAnchor.constraint(equalTo: view.rightAnchor),
      collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
      collectionView.topAnchor.constraint(equalTo: view.topAnchor)
    ]

    NSLayoutConstraint.activate(contraints)
  }

  private func makeDataSource() -> DataSource {
    let dataSource = DataSource(
      collectionView: collectionView,
      cellProvider: { (collectionView, indexPath, itemIdentifier) -> UICollectionViewCell? in
        let cell = collectionView.dequeueReusableCell(
          withReuseIdentifier: Self.cellIdentifier,
          for: indexPath
        )

        print("Log: configure cell in indexPath - \(indexPath)")
        cell.backgroundColor = itemIdentifier
        return cell
    })
    return dataSource
  }

  private func applySnapshot(animatingDifferences: Bool = true) {
    let items = (0...200).map { _ in
      UIColor(
        red: CGFloat.random(in: 0...1),
        green: CGFloat.random(in: 0...1),
        blue: CGFloat.random(in: 0...1),
        alpha: 1
      )
    }

    var snapshot = Snapshot()
    snapshot.appendSections([.main])
    snapshot.appendItems(items)
    dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
  }

  // MARK: - UICollectionViewDelegateFlowLayout

  func collectionView(
    _ collectionView: UICollectionView,
    layout collectionViewLayout: UICollectionViewLayout,
    sizeForItemAt indexPath: IndexPath
  ) -> CGSize {
    return CGSize(width: collectionView.bounds.width, height: collectionView.bounds.height)
  }

  // MARK: - UICollectionViewDataSourcePrefetching

  func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
    print("Log: prefetch cells in indexPaths - \(indexPaths)")
  }
}


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source