Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionView's prefetchItemsAt not being called

I'm trying to implement data prefetching for my UICollectionView using the UICollectionViewDataSourcePrefetching protocol; however, the respective method is not being called.
The collection view has a custom layout. I have also tried with the regular flow layout, same results.
Upon data reload, I execute the following code to make the collection view have the size of its content to prevent scrolling within that collection view but in a scroll view outside the collection view:

func reloadData() {
    viewDidLayoutSubviews()
    collectionView.reloadData()
    collectionView.layoutIfNeeded()
    viewDidLayoutSubviews()
}

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    collectionViewHeightConstraint.constant = collectionView.collectionViewLayout.collectionViewContentSize.height
    collectionView.layoutIfNeeded()
    view.layoutIfNeeded()
}

Maybe that has something to do with it?

What I have done:

  • my UIViewController does inherit from UICollectionViewDataSourcePrefetching
  • collectionView.prefetchDataSource = self (also tried using storyboard)
  • collectionView.isPrefetchingEnabled = true (also tried using storyboard)
  • I have implemented collectionView(_:prefetchItemsAt:)

Issue:

The prefetchItemsAt method is not being called. I determined that by placing a print statement in the method & setting up a breakpoint.

like image 636
LinusGeffarth Avatar asked Oct 16 '22 18:10

LinusGeffarth


2 Answers

Like requested in the comments, I'll share my implementation for this issue here:

I created the tracker prefetchState which determines whether I'm prefetching at the moment, or not:

enum PrefetchState {
    case fetching
    case idle
}
var prefetchState: PrefetchState = .idle

Then, I hooked up the scroll view's delegate (the scroll view my collection view is in) to my view controller and implemented the scrollViewDidScroll method:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    guard scrollView == self.scrollView else { return }

    let prefetchThreshold: CGFloat = 100 // prefetching will start 100pts above the bottom of the scroll view
    if scrollView.contentOffset.y > scrollView.contentSize.height-screenBounds.height-prefetchThreshold {
        if prefetchState == .idle {
            prefetchItems()
        }
    }
}

In there, you can see that I check whether we're already prefetching. If not, I call prefetchItems(), as implemented here:

func prefetchItems() {
    guard prefetchState == .idle else { return }
    prefetchState = .fetching
    someDownloadFuncWithCompletionBlock { (newItems) in
        self.dataSource += newItems
        self.collectionView.reloadData()
        self.prefetchState = .idle
    }
}
like image 77
LinusGeffarth Avatar answered Nov 15 '22 11:11

LinusGeffarth


I execute the following code to make the collection view have the size of its content to prevent scrolling within that collection view but in a scroll view outside the collection view:

This sounds very broken.

Why are you doing this?

Prefetching on the collection view (from the docs) is triggered when the user scrolls the collection view. By making the collection view frame the same as the content size you are essentially disabling the scrolling of the collection view itself.

The collection view calls this method as the user scrolls

If you are forcing the collection view frame to be the same as the content size then you are entirely breaking UICollectionView.

The reason the prefetch isn't called is because every cell has been loaded already. Nothing is in prefetch any more. Because your collection view is displaying every cell at the same time.

If you want to scroll a collection view... let the collection view handle it. You shouldn't need to place the collection view inside another scroll view.

like image 21
Fogmeister Avatar answered Nov 15 '22 10:11

Fogmeister