iPhone UIScrollView速度检查

39

我知道如何在UIScrollView移动时获取contentOffset,能否有人解释一下如何在UIScrollView跟踪或减速时获取表示当前速度的实际数字?


没什么大不了的,但我在底部提供了这个非常古老问题的现代解决方案。 - Fattie
8个回答

85

有一种更简单的方法:检查UISCrollview的pan手势识别器。使用它,您可以这样获取速度:

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

28
方便实用!唯一的缺点是,当用户松开手指时(这会结束滑动手势),scrollVelocity 立即变为 0.0f。因此,它是在拖动 scrollview 时测量速度的好方法,但在进行轻弹操作时无效。 - Kyle Fox
2
当然,panGestureRecognizer 仅在 iOS 5 及其以上版本中公开。 - bandejapaisa
滚动速度是否是一个点,因为它记录了垂直和水平速度? - rolling_codes
1
根据@KyleFox所说,当然是真的,可以利用scrollViewWillEndDragging:withVelocity:targetContentOffset:来处理当前触摸事件结束后的速度或偏移条件。 - Will Von Ullrich
1
那是触摸输入的速度,而不是scrollView的速度。 - Robin

60
在您的UIScrollViewDelegate上具有这些属性:
CGPoint lastOffset;
NSTimeInterval lastOffsetCapture;
BOOL isScrollingFast;

那么你可以在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;
    }
}

我得到了每毫秒的像素数,如果大于0.5,就标记为快速,小于0.5则标记为慢速。

我使用这个方法来加载一个表格视图上的一些单元格动画。如果在用户快速滚动时加载它们,则滚动效果不太好。


1
太棒了,这正是我计划中的。 - jfisk
4
谢谢提供一个好的解决方案,唯一需要改变的是将0.1替换为captureInterval常量,并且在计算scrollSpeedNotAbs时也使用它,以增加算法的可读性(因为大脑会停留在* 10,因为速度的一般公式是距离/时间)。 - zubko
2
很棒的ScrollView/CollectionView扩展!救了我的一天 :-) - PetrV
虽然这并不是错误的,但我在底部提供了这个非常古老问题的现代解决方案。干杯 - Fattie
@Fattie,我刚试了一下,似乎没问题。你为什么说它不正确? - Lance Samaria
@LanceSamaria 我说这并不是错误的,但它非常老式、缓慢和复杂,并且不完全符合现代 iOS 的工作方式。只需使用下面更简单的解决方案即可。 - Fattie

18

将@bandejapaisa的答案转换为Swift 5:

UIScrollViewDelegate使用的属性:

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

还有scrollViewDidScroll函数:

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

对于简单的速度计算(所有其他答案都更加复杂):

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

这也帮了我很多,谢谢! 其他人可能会考虑在其中加入fabsf(),以获得绝对值。 - Andrew
3
你需要考虑时间,否则速度会出现抖动。 - meaning-matters
除了抖动问题之外,另一个问题是如果我更改scrollView的contentInset,它将被注册为快速滚动。我很难区分这两个事件。 - Samuel Noyes

8

2017...

使用现代的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)")

当然,你想要以每秒点数为单位的速度,这就是它的含义。

人类在2017年的设备上拖动时速度大约为200-400个点每秒。

1000-3000是一个快速的投掷速度。

当它减速到停止时,20-30是很常见的。

因此,你经常会看到像这样的代码..

    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
    }

这是移动设备“浏览工程”的基础。(这是一个庞大而复杂的话题。)
请注意,这不是完整的浏览解决方案;您还需要关注像“它已停止”、“屏幕刚关闭”等异常情况。

6
也许这会有所帮助。
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset

想要提一下,这是从速度输出中的示例:(0.0,-5.35356)。在y轴上滚动,每次调用scrollViewDidScroll大约需要15个点 :) 因此,5就非常快了。 - Dima Deplov

3
您可以查看关于如何获取scrollview的contentOffset的示例代码PageControl。在移动时,可以通过查询scrollView.contentOffset来从UIScrollViewDelegate方法中获取contentOffset。当前速度可以通过delta_offset和delta_time计算。
  • Delta_offset = current_offset - pre_offset;
  • Delta_time = current_time - pre_time;

哦,我明白了。使用距离除以时间计算实时速度,我想是这样的。 - rolling_codes
1
在QuartzCore框架中,可以通过CACurrentMediaTime()获取当前时间。然后,您可以通过距离和时间计算速度。 - AechoLiu

1
这是在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.
    }
}

如果您是垂直滚动,应使用velocity.y;如果您是水平滚动,则应使用velocity.x。通常,如果值大于1且小于-1,则表示快速滚动。因此,您可以根据需要更改速度。+值表示向上滚动,-值表示向下滚动。


如何调整速度? - Just a coder
你是什么意思? :) - Chathuranga Silva
你在回答中说 “所以您可以根据需要更改速度”。我想知道,如何更改滚动速度? - Just a coder
1
我指的是用户滚动速度。这个函数循环捕获滚动速度,所以我是说你可以改变那个速度。你不能改变滚动速度 :) - Chathuranga Silva
这仅在视图被手指抛出时才相关。 - Fattie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接