InfiniteLayout
InfiniteLayout copied to clipboard
Infinitely looping collection view
Hello!
First of all wanted to thank you for such an amazing library! :)
I am trying to implement infinitely scrolling collection view with timer, so that it periodically slides to next indexPath.
Currently what I am doing is, I have function that looks like this, and I just call it every like 5 seconds.
func scrollToNextPage() {
guard let currentPage = collectionView.centeredIndexPath?.row else { return }
collectionView.scrollToItem(at: IndexPath(item: currentPage + 1, section: 0), at: .centeredHorizontally, animated: true)
But after some time, it throws an error:
'NSInvalidArgumentException', reason: 'attempt to scroll to invalid index path: <NSIndexPath: 0x282bb0980> {length = 2, path = 0 - 162}'
I have also trying to doing like this instead:
collectionView.scrollToItem(at: collectionView.indexPath(from: IndexPath(item: currentPage + 1, section: 0)), at: .centeredHorizontally, animated: true)
But the problem with this one is that when it gets to the last (supposedly last in the raw data source) item, when it scrolls to the first one it does it with animation of scrolling all the items back to the first one, but desired behavior is that it scrolls from the last item to the first with like "swiping right" animation, just ordinary scrolling to next item.
BR
Hello! Did you find solution?
Hi! You can try something like:
func scrollToNextPage() {
guard var currentPage = collectionView.centeredIndexPath?.row else { return }
if currentPage > collectionView.numberOfItems(inSection: 0) - 2 {
currentPage = 0
} else {
currentPage = currentPage + 1
}
collectionView.scrollToItem(at: IndexPath(item: currentPage, section: 0), at: .centeredHorizontally, animated: true)
}
Basically for this solution you need two things.
Observable.combineLatest(
viewModel.dataSource.asObservable().filter { !$0.isEmpty },
rx.viewDidAppear
).bind(onNext: { [weak self] _ in
self?.collectionView.beginShiftCounting()
})
and then inherit from RxInfiniteCollectionView
here is and example. 100% works fine
final class InfiniteCV: RxInfiniteCollectionView {
var autoScrollConfig: AutoScrollConfig?
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
super.scrollViewDidScroll(scrollView)
resetCounter()
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
beginShiftCounting()
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
beginShiftCounting()
}
var fakeCounter: EFCounter?
func resetCounter() {
guard fakeCounter != nil else { return }
fakeCounter?.stopCountAtCurrentValue()
fakeCounter?.reset()
fakeCounter?.invalidate()
fakeCounter = nil
}
func beginShiftCounting() {
guard let config = autoScrollConfig, config.direction != .none else { return }
resetCounter()
fakeCounter = EFCounter()
fakeCounter?.updateBlock = { [weak self] _ in
if let x = self?.contentOffset.x {
self?.delegate = nil // we must remove delegate to avoid delegate methods calling while we set content offset
let delta: CGFloat = (config.direction == .left ? 1 : -1) * CGFloat(config.speed)
self?.contentOffset = CGPoint(x: x + delta, y: 0)
self?.delegate = self // put delegate back
}
}
fakeCounter?.countFromZeroTo(99)
fakeCounter?.completionBlock = { [weak self] in
self?.resetCounter()
self?.beginShiftCounting()
}
}
}
EFCounter is CADisplayLink sugar
@voody2506 @nugmanoff @greenappleball please find my comment above