如何准确知道UIScrollView的滚动何时停止?

时间:2011-10-06 14:08:33

标签: objective-c ios uiscrollview

简而言之,我需要确切知道scrollview何时停止滚动。通过“停止滚动”,我的意思是它不再移动而不被触摸的那一刻。

我一直在研究一个水平的UIScrollView子类(适用于iOS 4),其中包含选择标签。它的一个要求是它停止滚动到一定速度以下,以便更快地进行用户交互。它还应该捕捉到选项卡的开头。换句话说,当用户释放滚动视图并且其速度较低时,它会捕捉到一个位置。我已经实现了它并且它可以工作,但它有一个错误。

我现在拥有的:

scrollview是它自己的委托。每次调用scrollViewDidScroll:时,它都会刷新与速度相关的变量:

-(void)refreshCurrentSpeed
{
    float currentOffset = self.contentOffset.x;
    NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970];

    deltaOffset = (currentOffset - prevOffset);
    deltaTime = (currentTime - prevTime);    
    currentSpeed = deltaOffset/deltaTime;
    prevOffset = currentOffset;
    prevTime = currentTime;

    NSLog(@"deltaOffset is now %f, deltaTime is now %f and speed is %f",deltaOffset,deltaTime,currentSpeed);
}

然后根据需要继续拍摄:

-(void)snapIfNeeded
{
    if(canStopScrolling && currentSpeed <70.0f && currentSpeed>-70.0f)
    {
        NSLog(@"Stopping with a speed of %f points per second", currentSpeed);
        [self stopMoving];

        float scrollDistancePastTabStart = fmodf(self.contentOffset.x, (self.frame.size.width/3));
        float scrollSnapX = self.contentOffset.x - scrollDistancePastTabStart;
        if(scrollDistancePastTabStart > self.frame.size.width/6)
        {
            scrollSnapX += self.frame.size.width/3;
        }
        float maxSnapX = self.contentSize.width-self.frame.size.width;
        if(scrollSnapX>maxSnapX)
        {
            scrollSnapX = maxSnapX;
        }
        [UIView animateWithDuration:0.3
                         animations:^{self.contentOffset=CGPointMake(scrollSnapX, self.contentOffset.y);}
                         completion:^(BOOL finished){[self stopMoving];}
        ];
    }
    else
    {
        NSLog(@"Did not stop with a speed of %f points per second", currentSpeed);
    }
}

-(void)stopMoving
{
    if(self.dragging)
    {
        [self setContentOffset:CGPointMake(self.contentOffset.x, self.contentOffset.y) animated:NO];
    }
    canStopScrolling = NO;
}

以下是委托方法:

-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    canStopScrolling = NO;
    [self refreshCurrentSpeed];
}

-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    canStopScrolling = YES;
    NSLog(@"Did end dragging");
    [self snapIfNeeded];
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    [self refreshCurrentSpeed];
    [self snapIfNeeded];
}

除了两种情况外,大部分时间都能正常运行: 1.当用户在没有松开他/她的手指的情况下滚动并且在移动后立即在接近静止的时刻进行滚动时,它经常按照它应该的方式捕捉到它的位置,但很多时候,它没有。通常需要几次尝试才能实现它。时间(非常低)和/或距离(相当高)的奇数值出现在释放时,导致高速值,而scrollView实际上几乎或完全静止。 2.当用户点击滚动视图以停止其移动时,似乎滚动视图将contentOffset设置为其先前的位置。这种远距传送产生非常高的速度值。这可以通过检查先前的delta是否为currentDelta * -1来解决,但我更喜欢更稳定的解决方案。

我尝试过使用didEndDecelerating,但是当出现故障时,它不会被调用。这可能证实它已经静止了。当scrollview完全停止移动时,似乎没有调用的委托方法。

如果您想亲眼看到故障,可以使用以下标签填写滚动视图:

@interface  UIScrollView <UIScrollViewDelegate>
{
    bool canStopScrolling;
    float prevOffset;
    float deltaOffset; //remembered for debug purposes
    NSTimeInterval prevTime;
    NSTimeInterval deltaTime; //remembered for debug purposes
    float currentSpeed;
}

-(void)stopMoving;
-(void)snapIfNeeded;
-(void)refreshCurrentSpeed;

@end


@implementation TabScrollView

-(id) init
{
    self = [super init];
    if(self)
    {
        self.delegate = self;
        self.frame = CGRectMake(0.0f,0.0f,320.0f,40.0f);
        self.backgroundColor = [UIColor grayColor];
        float tabWidth = self.frame.size.width/3;
        self.contentSize = CGSizeMake(100*tabWidth, 40.0f);
        for(int i=0; i<100;i++)
        {
            UIView *view = [[UIView alloc] init];
            view.frame = CGRectMake(i*tabWidth,0.0f,tabWidth,40.0f);
            view.backgroundColor = [UIColor colorWithWhite:(float)(i%2) alpha:1.0f];
            [self addSubview:view];
        }
    }
    return self;
}

@end

此问题的较短版本:如何知道滚动视图何时停止滚动?当didEndDecelerating:静止释放时,didEndDragging:不会被调用,{{1}}在滚动期间会发生很多事情,并且检查速度是不可靠的,因为这种奇怪的“跳跃”会将速度设置为随机的。

7 个答案:

答案 0 :(得分:30)

我找到了解决方案:

-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate

我之前没有注意到willDecelerate。当结束触摸时,scrollView静止时为false。结合上面提到的速度检查,我可以在它缓慢(不被触摸)或静止时拍摄。

对于没有进行任何捕捉但需要知道滚动停止时的任何人,将在滚动运动结束时调用didEndDecelerating。结合对willDeceleratedidEndDragging的检查,您就会知道滚动何时停止。

答案 1 :(得分:21)

[已编辑答案] 这是我使用的 - 它处理所有边缘情况。你需要一个ivar来保持状态,并且如评论中所示,还有其他方法来处理这个问题。

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    //[super scrollViewWillBeginDragging:scrollView];   // pull to refresh

    self.isScrolling = YES;
    NSLog(@"+scrollViewWillBeginDragging");
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    //[super scrollViewDidEndDragging:scrollView willDecelerate:decelerate];    // pull to refresh

    if(!decelerate) {
        self.isScrolling = NO;
    }
    NSLog(@"%@scrollViewDidEndDragging", self.isScrolling ? @"" : @"-");
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    self.isScrolling = NO;
    NSLog(@"-scrollViewDidEndDecelerating");
}

- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView
{   
    self.isScrolling = NO;
    NSLog(@"-scrollViewDidScrollToTop");
}

- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
    self.isScrolling = NO;
    NSLog(@"-scrollViewDidEndScrollingAnimation");
}

我创建了一个非常简单的项目使用上面的代码,这样当一个人与scrollView(包括WebView)交互时,它会禁止进程密集型工作,直到用户停止与scrollView交互并且scrollview停止滚动。这就像50行代码:ScrollWatcher

答案 2 :(得分:19)

以下是在滚动完成后如何组合scrollViewDidEndDeceleratingscrollViewDidEndDragging:willDecelerate来执行某些操作:

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

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

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

答案 3 :(得分:2)

本帖子中提到的委托方法答案对我没有帮助。我找到另一个答案,检测scrollViewDidScroll:链接中的最终滚动结尾为here

答案 4 :(得分:1)

我在我的博客上回答了这个问题,该问题概述了如何在没有问题的情况下做到这一点。

它涉及“拦截委托”以执行一些操作,然后将委托消息传递到它们的预期位置。这是必需的,因为有一些场景,如果代理以编程方式或不同时方式移动,则会触发,因此无论如何,最好只看下面的链接:

How to know when a UIScrollView is scrolling

这有点棘手,但我在那里提出了一个配方,并详细解释了这些部分。

答案 5 :(得分:0)

重要的是要理解,当UIScrollView停止移动时,它会触发UIScrollViewDelegate的两个不同功能,具体取决于用户按下的力度。

    当用户在屏幕上停止其拖动手势时,会触发
  1. stopDragging 。当这非常缓慢时,此后将不再发生任何运动。
  2. 当用户以某种方式按下滚动视图时,会触发
  3. stopDecelerating ,因此在用户触摸手指然后进入结束。当用户触摸手指后没有移动时,此功能并非总是触发,不会触发。然后仅触发“ stopDragging”上方的函数。

因此,总而言之,有必要像结合这两个功能。

在Swift 5.X中:

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

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if decelerate == false {
        myFunction()
    }
}

private func myFunction() {
    // scrollView did stop moving -> do what ever
    // ...
}

答案 6 :(得分:0)

这是我的 Swift 解决方案,适用于我已知的所有情况。注意:fab 是我显示和隐藏的按钮。

func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    hideFAB()
}

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if !decelerate {
        showFAB()
    } else {
        hideFAB()
    }
}

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

func showFAB() {
    fab.isHidden = false
}

func hideFAB() {
    fab.isHidden = true
}
相关问题