arnauddorgans / InfiniteLayout

Horizontal and Vertical infinite scrolling feature for UICollectionView with Paging, NSProxy delegate, Reactive extension, SectionModel & AnimatableSectionModel support
MIT License
516 stars 63 forks source link

Infinitely looping collection view #11

Open nugmanoff opened 5 years ago

nugmanoff commented 5 years ago

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

voody2506 commented 5 years ago

Hello! Did you find solution?

greenappleball commented 4 years ago

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)
}
astrokin commented 2 years ago

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

astrokin commented 2 years ago

@voody2506 @nugmanoff @greenappleball please find my comment above