Instagram / IGListKit

A data-driven UICollectionView framework for building fast and flexible lists.
https://instagram.github.io/IGListKit/
MIT License
12.84k stars 1.54k forks source link

Scrolling loses inertia when rendering more items from data source? #1128

Open achan opened 6 years ago

achan commented 6 years ago

New issue checklist

General information

Debug information

# Please include debug logs using the following lldb command:
po [IGListDebugger dump]

I'm still new to IGListKit so forgive me if I have this all wrong.

In two separate apps, one developed by me and another developed by a co-worker, we've seen scrolling inertia abruptly stop when trying to load more items to our UICollectionView (a la infinite scroll).

They're both implemented similarly to this:

// VideoFeedController.swift
override func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
  let distance = scrollView.contentSize.height - (targetContentOffset.pointee.y + scrollView.bounds.height)
  if !viewModel.isLoading && distance < 200 {
      loadMore()
  }
}

Where loadMore() is ultimately performing a network request, converting the request into ListDiffable view models, appending to the data source and telling the adapter to perform updates.

UICollectionViewController:

private func load(request: Promise<Void>) {
  adapter.performUpdates(animated: true, completion: nil)
  _ = request.done { [weak self] listing in
    guard let strongSelf = self else {
      return
    }

    strongSelf.adapter.performUpdates(animated: false, completion: nil)
    strongSelf.refresher.endRefreshing()
  }
}

View Model:

@discardableResult
func loadMore() -> Promise<Void> {
  isLoading = true
  return service.perform(pagination: pagination).done { [weak self] listing in
    self?.loaded(listing: listing)
  }
}

private func loaded(listing: VideoFeedListing) {
  isLoading = false
  items.append(contentsOf: listing.items)
  pagination = listing.pagination
}

I've played around with moving loadMore() to a background thread with similar outcome, though I believe network requests from URLSessionDataTask do that automatically?

Here's a screen capture of the bug in action. I've added a print statement for emphasis.

2018-03-16 11 45 48

Thanks for your time and all the hard work on this library 👏

rnystrom commented 6 years ago

My best guess to what's going on is that the target offset after scrolling finished is relative to the content size before the page finishes loading. Then the load finishes before the scrolling animation is done, but the resting offset has already been determined. So your new content loads but the scrolling just ends.

One idea to fix this is to make your loading trigger distance way larger (on Instagram its like 2x your screen height, pretty large).

szubiszon commented 4 years ago

@achan Did you find a solution? I have the same problem. @rnystrom I often get stuck when scrolling very quickly and adding new objects model.append(contentsOf: newElements) listAdapter?.performUpdates(animated: false, completion: nil)

achan commented 4 years ago

I did not, @szubiszon. It was a side-project that eventually got dropped 😢

aspek commented 4 years ago

I've solved this issue by using

let oldItem = model.items
olditem.append(newItems)
model.items = olditem

//then
listAdapter?.performUpdates(animated: false, completion: nil)