Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect when a UIScrollView has finished scrolling

The 320 implementations are so much better - here is a patch to get consistent start/ends of the scroll.

-(void)scrollViewDidScroll:(UIScrollView *)sender 
{   
[NSObject cancelPreviousPerformRequestsWithTarget:self];
    //ensure that the end of scroll is fired.
    [self performSelector:@selector(scrollViewDidEndScrollingAnimation:) withObject:sender afterDelay:0.3]; 

...
}

-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
...
}

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

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

- (void)stoppedScrolling {
    // ...
}

For all scrolls related to dragging interactions, this will be sufficient:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    _isScrolling = NO;
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    if (!decelerate) {
        _isScrolling = NO;
    }
}

Now, if your scroll is due to a programmatic setContentOffset/scrollRectVisible (with animated = YES or you obviously know when scroll is ended):

 - (void)scrollViewDidEndScrollingAnimation {
     _isScrolling = NO;
}

If your scroll is due to something else (like keyboard opening or keyboard closing), it seems like you'll have to detect the event with a hack because scrollViewDidEndScrollingAnimation is not useful either.

The case of a PAGINATED scroll view:

Because, I guess, Apple apply an acceleration curve, scrollViewDidEndDecelerating get called for every drag so there's no need to use scrollViewDidEndDragging in this case.


I think scrollViewDidEndDecelerating is the one you want. Its UIScrollViewDelegates optional method:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

Tells the delegate that the scroll view has ended decelerating the scrolling movement.

UIScrollViewDelegate documentation


This has been described in some of the other answers, but here's (in code) how to combine scrollViewDidEndDecelerating and scrollViewDidEndDragging:willDecelerate to perform some operation when scrolling has finished:

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

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

- (void)stoppedScrolling
{
    // done, do whatever
}

I only just found this question, which is pretty much the same I asked: How to know exactly when a UIScrollView's scrolling has stopped?

Though didEndDecelerating works when scrolling, panning with stationary release does not register.

I eventually found a solution. didEndDragging has a parameter WillDecelerate, which is false in the stationary release situation.

By checking for !decelerate in DidEndDragging, combined with didEndDecelerating, you get both situations that are the end of scrolling.


func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    scrollingFinished(scrollView: scrollView)
}

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if decelerate {
        //didEndDecelerating will be called for sure
        return
    }
    scrollingFinished(scrollView: scrollView)        
}

func scrollingFinished(scrollView: UIScrollView) {
   // Your code
}