如何检测UIScrollView何时完成滚动

时间:2009-06-14 17:37:20

标签: ios iphone uiscrollview

UIScrollViewDelegate有两个委托方法scrollViewDidScroll:scrollViewDidEndScrollingAnimation:,但这些方法都没有告诉你滚动完成的时间。 scrollViewDidScroll仅通知您滚动视图确实滚动而不是滚动完成。

如果您以编程方式移动滚动视图而不是用户滚动,则其他方法scrollViewDidEndScrollingAnimation似乎只会触发。

有没有人知道滚动视图何时完成滚动检测的方案?

19 个答案:

答案 0 :(得分:166)

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];
...
}

答案 1 :(得分:143)

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

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

- (void)stoppedScrolling {
    // ...
}

答案 2 :(得分:21)

我认为scrollViewDidEndDecelerating是你想要的。它的UIScrollViewDelegates可选方法:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

告诉代理滚动视图已经结束减速滚动动作。

UIScrollViewDelegate documentation

答案 3 :(得分:20)

对于与拖动交互相关的所有滚动,这就足够了:

- (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也没用。

PAGINATED 滚动视图的情况:

因为,我猜,Apple会应用加速度曲线,scrollViewDidEndDecelerating会调用每个拖动,因此在这种情况下无需使用scrollViewDidEndDragging

答案 4 :(得分: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
}

答案 5 :(得分:7)

我刚刚发现了这个问题,这跟我问的几乎一样: How to know exactly when a UIScrollView's scrolling has stopped?

尽管在滚动时didEndDecelerating正常工作,但是使用固定释放进行平移不会注册。

我最终找到了解决方案。 didEndDragging有一个参数WillDecelerate,在静止释放情况下是假的。

通过在DidEndDragging中检查!减速,结合didEndDecelerating,你得到两种滚动结束的情况。

答案 6 :(得分:3)

接受答案的Swift版本:

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
}

答案 7 :(得分:3)

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

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    if decelerate {
        //didEndDecelerating will be called for sure
        return
    }
    else {
        scrollingFinished(scrollView: scrollView)
    }
}

func scrollingFinished(scrollView: UIScrollView) {
   // Your code
}

答案 8 :(得分:3)

我尝试过Ashley Smart的答案,它就像一个魅力。这是另一个想法,只使用scrollViewDidScroll

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

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

答案 9 :(得分:2)

刚刚开发出的解决方案可以检测滚动完成整个应用范围的时间: https://gist.github.com/k06a/731654e3168277fb1fd0e64abc7d899e

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

这是跟踪runloop模式更改iOS10 +的核心思路:

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

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

适用于iOS2 +等低部署目标的解决方案:

- (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 ]];
}

答案 10 :(得分:1)

我有一个点击和拖动操作的情况,我发现拖动调用scrollViewDidEndDecelerating

手动更改偏移代码([_scrollView setContentOffset:contentOffset animated:YES];)正在调用scrollViewDidEndScrollingAnimation。

//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
}

答案 11 :(得分:1)

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

在包含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

答案 12 :(得分:1)

如果有人需要,请参阅Swift中的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)
    ...
}

答案 13 :(得分:1)

另一种方法是使用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") } 中使用此内容偏移可以正确识别滚动视图何时停止滚动。

selectedParks

答案 14 :(得分:0)

UIScrollview有一个委托方法

namespace Test\Firstmodule\Controller\Index;
class Index extends \Magento\Framework\App\Action\Action
{

    public function execute()
   {
        $model = $this->_objectManager->create('Test\FirstModule\Model\Firstmodule');
        $model->setTitle('What is Question ?');
        $model->save();
        $this->_view->loadLayout();
        $this->_view->getLayout()->initMessages();
        $this->_view->renderLayout();
   }
}

在委托方法

中添加以下代码行
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

答案 15 :(得分:0)

有一种UIScrollViewDelegate的方法可以用来检测(或更好地说'预测')滚动时间真的完成:

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

可用于检测(或更好地说'预测')滚动时间确实完成。

在我的情况下,我使用水平滚动如下(在 Swift 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
}

答案 16 :(得分:0)

如果您喜欢Rx,可以像这样扩展UIScrollView:

import RxSwift
import RxCocoa

extension Reactive where Base: UIScrollView {
    public var didEndScrolling: ControlEvent<Void> {
        let source = Observable
            .merge([base.rx.didEndDragging.map { !$0 },
                    base.rx.didEndDecelerating.mapTo(true)])
            .filter { $0 }
            .mapTo(())
        return ControlEvent(events: source)
    }
}

这将使您可以做到这一点:

scrollView.rx.didEndScrolling.subscribe(onNext: {
    // Do what needs to be done here
})

这将同时考虑拖动和减速。

答案 17 :(得分:0)

使用Ashley Smart逻辑并将其转换为Swift 4.0及更高版本。

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

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

上面的逻辑解决了诸如用户滚动到表视图时的问题。没有逻辑,当您滚动离开表视图时,didEnd将被调用,但将不执行任何操作。当前在2020年使用它。

答案 18 :(得分:0)

在某些早期的iOS版本(如iOS 9、10)上,如果通过触摸突然停止了滚动视图,则不会触发systemctl start shinyproxy

但是在当前版本(iOS 13)中,scrollViewDidEndDecelerating将被确定触发(据我所知)。

因此,如果您的应用程序也以较早版本为目标,则可能需要一种替代方法,例如Ashley Smart提到的替代方法,或者可以采用以下替代方法。

scrollViewDidEndDecelerating

说明

UIScrollView将以三种方式停止:
-快速滚动并自行停止
-通过手指触摸快速滚动并停止(例如紧急制动)
-缓慢滚动并停止

第一个可以通过 func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { if !scrollView.isTracking, !scrollView.isDragging, !scrollView.isDecelerating { // 1 scrollViewDidEndScrolling(scrollView) } } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if !decelerate, scrollView.isTracking, !scrollView.isDragging, !scrollView.isDecelerating { // 2 scrollViewDidEndScrolling(scrollView) } } func scrollViewDidEndScrolling(_ scrollView: UIScrollView) { // Do something here } 和其他类似方法检测,而另两个则不能。

幸运的是,scrollViewDidEndDecelerating具有三种状态,我们可以使用它们来识别它们,这两种状态在用“ // 1”和“ // 2”注释的两行中使用。