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

总之,我需要知道滚动视图何时停止滚动。 通过“停止滚动”,我指的是不再移动而不被触摸的那一刻。

我一直在水平的UIScrollView子类(iOS 4)与select选项卡在其中。 其中一个要求是它停止滚动低于一定的速度,以允许用户更快地交互。 它也应该捕捉到一个标签的开始。 换句话说,当用户释放滚动视图并且其速度低时,它将捕捉到一个位置。 我已经实现了这个,它的工作原理,但它有一个错误。

我现在拥有的:

scrollview是它自己的委托。 在每次调用scrollViewDidScroll时,刷新其速度相关的variables:

-(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.用户在不释放手指的情况下滚动并在移动之后立即离开近乎静止的时间点时,它经常会按照其应有的位置alignment,而是很多次,没有。 通常需要一些尝试才能实现。 在释放时出现时间(非常低)和/或距离(相当高)的奇数值,导致高速值,而实际上,scrollView几乎或完全静止。 2.当用户点击滚动视图以停止移动时,滚动视图似乎将contentOffset设置为其前一个点。 这个传送速度非常高。 这可以通过检查以前的增量是currentDelta * -1来解决,但我更喜欢更稳定的解决scheme。

我试过使用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:在滚动期间会发生很多事情,检查速度是不可靠的,因为这种奇怪的“跳跃” didEndDragging:速度设置为随机的。

我find一个解决scheme:

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

我没有注意到,最后一点,会willDecelerate 。 当结束触摸时,scrollView静止不动。 结合上面提到的速度检查,我可以在速度缓慢(而不是被触摸)或静止时捕捉。

对于没有捕捉到但需要知道什么时候滚动停止的人,在滚动didEndDecelerating会调用didEndDecelerating 。 结合willDecelerate中的didEndDragging检查,您将知道滚动何时停止。

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

 - (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"); } 

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

以下是如何结合scrollViewDidEndDeceleratingscrollViewDidEndDragging:willDecelerate当滚动完成时,将执行一些操作的scrollViewDidEndDragging:willDecelerate

 - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [self stoppedScrolling]; } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { if (!decelerate) { [self stoppedScrolling]; } } - (void)stoppedScrolling { // done, do whatever } 

在这个post中提到的代表方法的答案没有帮助我。 我发现另一个检测scrollViewDidScroll:最终滚动结束的scrollViewDidScroll:链接在这里

我在我的博客上回答了这个问题,概述了如何做到没有问题。

它涉及到“拦截委托人”做一些事情,然后将委托消息传递到他们想要的地方。 这是必需的,因为有一些情况下委托如果以编程方式移动或不移动,或者两者兼而有之,则无论如何,最好只查看下面的链接:

如何知道UIScrollView何时滚动

这是一个有点棘手,但我已经提出了一个配方,并详细解释了部分。