ChatGPT解决这个技术问题 Extra ChatGPT

UICollectionView: how to detect when scrolling has stopped

I'm using a UICollectionView to scroll through a set of thumbnails quickly. Once scrolling ends, I'd like to display a larger hi-res version of the current thumbnail.

How can I detect when the user has completed scrolling? I do implement didEndDisplayingCell, but that only tells me when a particular cell has scrolled off; it doesn't tell me when the scroll motion actually completes.


i
iDev
NS_CLASS_AVAILABLE_IOS(6_0) @interface UICollectionView : UIScrollView

UICollectionView is a subclass of UIScrollView. So if you have set the delegate and implemented UIScrollViewDelegate, you should be able to detect this the same way as UIScrollView.

For eg:-

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;

As per documentation, the above method should tell when the scroll view has ended decelerating the scrolling movement.


Brilliant. You don't know how much nasty code this saved me. I was trying to monitor it using the shouldInvalidateLayoutForBoundsChange callback in my UICollectionViewFlowLayout. What a n00b.
Please see D6mi's note below about scrollViewDidEndScrollingAnimation. scrollViewDidEndDecelerating does not get called for programmatic scrolling.
Indeed, I don't understand why the current answer is the accepted one, it does not work scrollViewDidEndDecelerating is not called but like D6mi said, scrollViewDidEndScrollingAnimation is called
This answer is not complete.
Brilliant answer. @Nathanael, the answer is complete since he pointed out that UICollectionView is a subclass of UIScrollView and also quoted an eg:- delegate method. Rest of the answers just took this answer and added other delegate methods to it. Ideally rest should be just a comment on this answer. The keypart is collectionview is just a scrollview, rest can be figured out.
A
Abey M

Just to cover your bases you should implement both these UIScrollViewDelegate methods. In some cases there may not be a deceleration (and scrollViewDidEndDecelerating would not be called), for e.g., the page is fully scrolled in place. In those case do your update right there in scrollViewDidEndDragging.

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
  if (!decelerate) {
    [self updateStuff];
  }
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
  [self updateStuff];
}

Thank you for sharing this! This should be the accepted answer b/c OP asked "scrolling has stopped" -- without both methods in place the user can easily scroll the collection view down and release finger without flicking and scrollViewDidEndDecelerating will NOT get called, scrollViewDidEndDragging does. Thank you for posting this answer.
D
D6mi

An important fact to note here.

This method gets called on User initiated scrolls (i.e a Pan gesture):

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;

or in Swift:

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView)


On the other hand, this one gets called on all manually (programatically) initiated scrolls (like scrollRectToVisible or scrollToItemAtIndexPath):

- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView

or in Swift:

func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView)


d
drpawelo

Swift 3 version of Abey M and D6mi 's answers:

When scroll is caused by user action

public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if (!decelerate) {
        //cause by user
        print("SCROLL scrollViewDidEndDragging")
    }
}

public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    //caused by user
    print("SCROLL scrollViewDidEndDecelerating")
}

When scroll is caused by code action (programmatically): (like "scrollRectToVisible" or "scrollToItemAtIndexPath")

public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
    //caused by code
    print("SCROLL scrollViewDidEndScrollingAnimation")
}

Notes:

Put these functions in your UIScrollViewDelegate or UICollectionViewDelegate delegate.

if you don't have a separate delegate, make your current class extend a UIScrollViewDelegate op top of your class file

.

open class MyClass: NSObject , UICollectionViewDelegate

and somewhere in your viewWillAppear make the class its own delegate

override open func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    // ...
    self.myScrollView.delegate = self
    // ...
}

M
Mr Stanev

Swift 3 version:

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    // Your code here
}

O
Ofir Malachi

if you want to use the visible indexpath:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    [self scrollingFinish];
}
- (void)scrollingFinish {


    if([self.collectionView indexPathsForVisibleSupplementaryElementsOfKind:UICollectionElementKindSectionHeader]){
        NSIndexPath *firstVisibleIndexPath = [[self.collectionView indexPathsForVisibleSupplementaryElementsOfKind:UICollectionElementKindSectionHeader] firstObject];
        [self.collectionView scrollToItemAtIndexPath:firstVisibleIndexPath atScrollPosition:UICollectionViewScrollPositionTop animated:YES];
        [NSObject cancelPreviousPerformRequestsWithTarget:self];
    }
}