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

Abe*_*ant 26 objective-c uiscrollview ios

简而言之,我需要确切知道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);
}
Run Code Online (Sandbox Code Playgroud)

然后根据需要继续捕捉:

-(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;
}
Run Code Online (Sandbox Code Playgroud)

以下是委托方法:

-(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];
}
Run Code Online (Sandbox Code Playgroud)

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

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

如果您想亲眼看到这些故障,可以使用以下选项卡填充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
Run Code Online (Sandbox Code Playgroud)

这个问题的一个较短版本:如何知道滚动视图何时停止滚动?didEndDecelerating:当你静止释放它时不会被调用,didEndDragging:在滚动过程中会发生很多事情,并且检查速度是不可靠的,因为这种奇怪的"跳跃" 会将速度设置为随机的.

Abe*_*ant 30

我找到了解决方案:

-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
Run Code Online (Sandbox Code Playgroud)

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

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


Dav*_*d H 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");
}
Run Code Online (Sandbox Code Playgroud)

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

  • 这位先生,对我手头的问题是最干净的解决方案,我已经看到了.谢谢. (2认同)

Way*_*ett 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
}
Run Code Online (Sandbox Code Playgroud)