37
votes

I know how to get the contentOffset on movement for a UIScrollView, can someone explain to me how I can get an actual number that represents the current speed of a UIScrollView while it is tracking, or decelerating?

8
Not a big deal but I put in the modern solution to this very old question down the bottom.Fattie

8 Answers

57
votes

Have these properties on your UIScrollViewDelegate

CGPoint lastOffset;
NSTimeInterval lastOffsetCapture;
BOOL isScrollingFast;

Then have this code for your scrollViewDidScroll:

- (void) scrollViewDidScroll:(UIScrollView *)scrollView {    
    CGPoint currentOffset = scrollView.contentOffset;
    NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];

    NSTimeInterval timeDiff = currentTime - lastOffsetCapture;
    if(timeDiff > 0.1) {
        CGFloat distance = currentOffset.y - lastOffset.y;
        //The multiply by 10, / 1000 isn't really necessary.......
        CGFloat scrollSpeedNotAbs = (distance * 10) / 1000; //in pixels per millisecond

        CGFloat scrollSpeed = fabsf(scrollSpeedNotAbs);
        if (scrollSpeed > 0.5) {
            isScrollingFast = YES;
            NSLog(@"Fast");
        } else {
            isScrollingFast = NO;
            NSLog(@"Slow");
        }        

        lastOffset = currentOffset;
        lastOffsetCapture = currentTime;
    }
}

And from this i'm getting pixels per millisecond, which if is greater than 0.5, i've logged as fast, and anything below is logged as slow.

I use this for loading some cells on a table view animated. It doesn't scroll so well if I load them when the user is scrolling fast.

85
votes

There's an easier way: check the UISCrollview's pan gesture recognizer. With it, you can get the velocity like so:

CGPoint scrollVelocity = [[_scrollView panGestureRecognizer] velocityInView:self];
15
votes

Converted @bandejapaisa answer to Swift 5:

Properties used by UIScrollViewDelegate:

var lastOffset: CGPoint = .zero
var lastOffsetCapture: TimeInterval = .zero
var isScrollingFast: Bool = false

And the scrollViewDidScroll function:

func scrollViewDidScroll(scrollView: UIScrollView) {

    let currentOffset = scrollView.contentOffset
    let currentTime = Date.timeIntervalSinceReferenceDate
    let timeDiff = currentTime - lastOffsetCapture
    let captureInterval = 0.1
    
    if timeDiff > captureInterval {
        
        let distance = currentOffset.y - lastOffset.y     // calc distance
        let scrollSpeedNotAbs = (distance * 10) / 1000     // pixels per ms*10
        let scrollSpeed = fabsf(Float(scrollSpeedNotAbs))  // absolute value
        
        if scrollSpeed > 0.5 {
            isScrollingFast = true
            print("Fast")
        } else {
            isScrollingFast = false
            print("Slow")
        }
        
        lastOffset = currentOffset
        lastOffsetCapture = currentTime
        
    }
}
12
votes

For a simple speed calculation (All the other answers are more complicated):

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    CGFloat scrollSpeed = scrollView.contentOffset.y - previousScrollViewYOffset;
    previousTableViewYOffset = scrollView.contentOffset.y;
}
7
votes

2017...

It's very easy to do this with modern Swift/iOS:

var previousScrollMoment: Date = Date()
var previousScrollX: CGFloat = 0

func scrollViewDidScroll(_ scrollView: UIScrollView) {
        
    let d = Date()
    let x = scrollView.contentOffset.x
    let elapsed = Date().timeIntervalSince(previousScrollMoment)
    let distance = (x - previousScrollX)
    let velocity = (elapsed == 0) ? 0 : fabs(distance / CGFloat(elapsed))
    previousScrollMoment = d
    previousScrollX = x
    print("vel \(velocity)")

Of course you want the velocity in points per second, which is what that is.

Humans drag at say 200 - 400 pps (on 2017 devices).

1000 - 3000 is a fast throw.

As it slows down to a stop, 20 - 30 is common.

So very often you will see code like this ..

    if velocity > 300 {
    
        // the display is >skimming<
        some_global_doNotMakeDatabaseCalls = true
        some_global_doNotRenderDiagrams = true
    }
    else {
    
        // we are not skimming, ok to do calculations
        some_global_doNotMakeDatabaseCalls = false
        some_global_doNotRenderDiagrams = false
    }

This is the basis for "skimming engineering" on mobiles. (Which is a large and difficult topic.)

Note that that is not a complete skimming solution; you also have to care for unusual cases like "it has stopped" "the screen just closed" etc etc.

6
votes

May be this would be helpful

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
3
votes

You can see PageControl sample code about how to get the contentOffset of scrollview.

The contentOffset on movement can be obtained from UIScrollViewDelegate method, named - (void)scrollViewDidScroll:(UIScrollView *)scrollView, by querying scrollView.contentOffset. Current speed can be calculated by delta_offset and delta_time.

  • Delta_offset = current_offset - pre_offset;
  • Delta_time = current_time - pre_time;
1
votes

Here is another smart way to do this in SWIFT :-

func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    if velocity.y > 1.0 || velocity.y < -1.0 && self.sendMessageView.isFirstResponder() {
        // Somthing you want to do when scrollin fast.
        // Generally fast Vertical scrolling.
    }
}

So if you scrolling vertically you should use velocity.y and also if you are scrolling horizontally you should use velocity.x . Generally if value is more than 1 and less than -1, it represent generally fast scrolling. So you can change the speed as you want. +value means scrolling up and -value means scrolling down.