如何检测UIScrollView何时完成滚动

问题描述:

UIScrollViewDelegate有两个代理方法scrollViewDidScroll:scrollViewDidEndScrollingAnimation:但这两个方法都告诉您滚动何时完成。 scrollViewDidScroll只会通知您滚动视图确实滚动而不是滚动完成。如何检测UIScrollView何时完成滚动

另一种方法scrollViewDidEndScrollingAnimation似乎只有在用户滚动时以编程方式移动滚动视图时才会触发。

有谁知道方案来检测滚动视图完成滚动时?

+0

也看,如果你想通过程序滚动后,检测成品滚动:http://stackoverflow.com/questions/2358046/is-it-possible-to-be-notified-when-a-uitableview-完成滚动 – Trevor 2011-07-07 02:53:36

您正在查找的方法是scrollViewDidEndDragging:willDecelerate:scrollViewDidEndDecelerating:。第一个通常在用户抬起手指后调用。如果它们的滚动速度足够快,导致减速,willDecelerate将为YES,第二种方法将在减速完成后调用。

From the UIScrollViewDelegate docs.

+10

看起来只有在减速时才起作用。如果你慢慢地平移,然后释放,它不会调用didEndDecelerating。 – 2010-11-01 23:00:33

+3

虽然它是官方的API。它实际上并不总是像我们预期的那样工作。 @Ashley Smart提供了更实用的解决方案。 – 2011-06-14 08:45:43

+0

我编辑了答案来澄清这个案子,肖恩。 – 2011-11-08 16:22:46

我觉得scrollViewDidEndDecelerating是你想要的。它的UIScrollViewDelegates可选方法:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 

告知委托人滚动视图已结束,从而减慢滚动动作。

UIScrollViewDelegate documentation

320 implementations是好多了 - 这里是一个补丁来获得滚动一致的开始/结束。

-(void)scrollViewDidScroll:(UIScrollView *)sender 
{ 
[NSObject cancelPreviousPerformRequestsWithTarget:self]; 
    //ensure that the end of scroll is fired. 
    [self performSelector:@selector(scrollViewDidEndScrollingAnimation:) withObject:sender afterDelay:0.3]; 

... 
} 

-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView 
{ 
    [NSObject cancelPreviousPerformRequestsWithTarget:self]; 
... 
} 

我试过阿什利智能的答案,它的工作就像一个魅力。这是另一个想法,只使用scrollViewDidScroll

-(void)scrollViewDidScroll:(UIScrollView *)sender 
{ 
    if(self.scrollView_Result.contentOffset.x == self.scrollView_Result.frame.size.width)  { 
    // You have reached page 1 
    } 
} 

我刚刚有两页,所以它为我工作。但是,如果您有多个页面,则可能存在问题(您可以检查当前偏移量是否为宽度的倍数,但是您不知道用户是停在第2页还是正在前往第3页或更多)

我才刚刚发现了这个问题,这是相当多的,我问是相同的: How to know exactly when a UIScrollView's scrolling has stopped?

虽然didEndDecelerating滚动时,具有固定的版本不注册平移工作。

我终于找到了解决方案。 didEndDragging具有WillDecelerate参数,在静态释放情况下为false。

通过在DidEndDragging中检查!decelerate,并结合didEndDecelerating,您将得到两种情况即滚动结束。

对于与拖动交互的所有卷轴,这将是不够的:现在

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { 
    _isScrolling = NO; 
} 

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { 
    if (!decelerate) { 
     _isScrolling = NO; 
    } 
} 

,如果您的滚动是由于程序setContentOffset/scrollRectVisible(与animated = YES或者你明明知道,当滚动结束):

- (void)scrollViewDidEndScrollingAnimation { 
    _isScrolling = NO; 
} 

如果您的滚动是由于其他的东西(如键盘打开或关闭键盘),好像你必须与一个黑客以检测事件,因为scrollViewDidEndScrollingAnimation是没有用的两种。

一个分页的滚动视图的情况下:

因为,我想,苹果申请的加速曲线,scrollViewDidEndDecelerating被调用每一个阻力,所以没有必要在这种情况下使用scrollViewDidEndDragging

回顾(和新手)。这并不是那么痛苦。只需添加协议,然后添加您需要检测的功能。

在包含UIScrolView的视图(类)中,添加协议,然后从这里添加任何函数到您的视图(类)。

// -------------------------------- 
// In the "h" file: 
// -------------------------------- 
@interface myViewClass : UIViewController <UIScrollViewDelegate> // <-- Adding the protocol here 

// Scroll view 
@property (nonatomic, retain) UIScrollView *myScrollView; 
@property (nonatomic, assign) BOOL isScrolling; 

// Protocol functions 
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView 
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView; 
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView; 


// -------------------------------- 
// In the "m" file: 
// -------------------------------- 
@implementation BlockerViewController 

- (void)viewDidLoad { 
    CGRect scrollRect = self.view.frame; // Same size as this view 
    self.myScrollView = [[UIScrollView alloc] initWithFrame:scrollRect]; 
    self.myScrollView.delegate = self; 
    self.myScrollView.contentSize = CGSizeMake(scrollRect.size.width, scrollRect.size.height); 
    self.myScrollView.contentInset = UIEdgeInsetsMake(0.0,22.0,0.0,22.0); 
    // Allow dragging button to display outside the boundaries 
    self.myScrollView.clipsToBounds = NO; 
    // Prevent buttons from activating scroller: 
    self.myScrollView.canCancelContentTouches = NO; 
    self.myScrollView.delaysContentTouches = NO; 
    [self.myScrollView setBackgroundColor:[UIColor darkGrayColor]]; 
    [self.view addSubview:self.myScrollView]; 

    // Add stuff to scrollview 
    UIImage *myImage = [UIImage imageNamed:@"foo.png"]; 
    [self.myScrollView addSubview:myImage]; 
} 

// Protocol functions 
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { 
    NSLog(@"start drag"); 
    _isScrolling = YES; 
} 

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { 
    NSLog(@"end decel"); 
    _isScrolling = NO; 
} 

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { 
    NSLog(@"end dragging"); 
    if (!decelerate) { 
     _isScrolling = NO; 
    } 
} 

// All of the available functions are here: 
// https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIScrollViewDelegate_Protocol/Reference/UIScrollViewDelegate.html 

这已经在一些其他的答案中进行了描述,但这里的(代码),当滚动完成如何scrollViewDidEndDeceleratingscrollViewDidEndDragging:willDecelerate结合执行一些操作:

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

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

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

我有一个案子点击和拖动动作,我发现拖动调用scrollViewDidEndDecelerating

和手动更改代码([_scrollView setContentOffset:contentOffset animated:YES];);手动偏移调用scrollView DidEndScrollingAnimation。

//This delegate method is called when the dragging scrolling happens, but no when the  tapping 
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 
{ 
    //do whatever you want to happen when the scroll is done 
} 

//This delegate method is called when the tapping scrolling happens, but no when the dragging 
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView 
{ 
    //do whatever you want to happen when the scroll is done 
} 

接受的答案的斯威夫特版本:

func scrollViewDidScroll(scrollView: UIScrollView) { 
    // example code 
} 
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) { 
     // example code 
} 
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView!, atScale scale: CGFloat) { 
     // example code 
} 

的UIScrollView有一个委托方法

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 

添加的代码下面的线路中的委托方法

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 
{ 
CGSize scrollview_content=scrollView.contentSize; 
CGPoint scrollview_offset=scrollView.contentOffset; 
CGFloat size=scrollview_content.width; 
CGFloat x=scrollview_offset.x; 
if ((size-self.view.frame.size.width)==x) { 
    //You have reached last page 
} 
} 

如果有人需要,这里是Ashley Smart在斯威夫特回答

func scrollViewDidScroll(_ scrollView: UIScrollView) { 
     NSObject.cancelPreviousPerformRequests(withTarget: self) 
     perform(#selector(UIScrollViewDelegate.scrollViewDidEndScrollingAnimation), with: nil, afterDelay: 0.3) 
    ... 
} 

func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { 
     NSObject.cancelPreviousPerformRequests(withTarget: self) 
    ... 
} 

刚开发的解决方案滚动成品应用范围时,检测: https://gist.github.com/k06a/731654e3168277fb1fd0e64abc7d899e

它基于的runloop模式跟踪变化的想法。至少在滚动后0.2秒后执行块。

这是用于跟踪runloop模式核心思想变化iOS10 +:对于像iOS2 +低部署目标

- (void)tick { 
    [[NSRunLoop mainRunLoop] performInModes:@[ UITrackingRunLoopMode ] block:^{ 
     [self tock]; 
    }]; 
} 

- (void)tock { 
    self.runLoopModeWasUITrackingAgain = YES; 
    [[NSRunLoop mainRunLoop] performInModes:@[ NSDefaultRunLoopMode ] block:^{ 
     [self tick]; 
    }]; 
} 

和溶液:

- (void)tick { 
    [[NSRunLoop mainRunLoop] performSelector:@selector(tock) target:self argument:nil order:0 modes:@[ UITrackingRunLoopMode ]]; 
} 

- (void)tock { 
    self.runLoopModeWasUITrackingAgain = YES; 
    [[NSRunLoop mainRunLoop] performSelector:@selector(tick) target:self argument:nil order:0 modes:@[ NSDefaultRunLoopMode ]]; 
} 

没有可被用于检测的UIScrollViewDelegate的方法(或更好地说'预测')当滚动真的完成时:

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) 

of UIScrollViewDelegate它可以用于检测(或更好地说'预测')滚动时已经完成。

在我的情况我与水平滚动用它作为以下(在夫特3):

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { 
    perform(#selector(self.actionOnFinishedScrolling), with: nil, afterDelay: Double(velocity.x)) 
} 
func actionOnFinishedScrolling() { 
    print("scrolling is finished") 
    // do what you need 
} 

另一种方法是使用scrollViewWillEndDragging:withVelocity:targetContentOffset被称为每当用户提起手指和包含目标内容偏移将停止滚动。在scrollViewDidScroll:中使用此内容偏移可正确识别滚动视图何时停止滚动。

private var targetY: CGFloat? 
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, 
             withVelocity velocity: CGPoint, 
             targetContentOffset: UnsafeMutablePointer<CGPoint>) { 
     targetY = targetContentOffset.pointee.y 
} 
public func scrollViewDidScroll(_ scrollView: UIScrollView) { 
    if (scrollView.contentOffset.y == targetY) { 
     print("finished scrolling") 
    }