Objective c 如何准确知道UIScrollView';s的滚动停止了吗?

Objective c 如何准确知道UIScrollView';s的滚动停止了吗?,objective-c,ios,uiscrollview,Objective C,Ios,Uiscrollview,简而言之,我需要知道scrollview何时停止滚动。所谓“停止滚动”,我指的是它不再移动,也不再被触摸的时刻 我一直在开发一个水平的UIScrollView子类(适用于iOS 4),其中包含选择选项卡。它的一个要求是在低于一定速度时停止滚动,以允许用户更快地交互。它还应该捕捉到选项卡的开头。换句话说,当用户释放scrollview且其速度较低时,它会捕捉到一个位置。我已经实现了这一点,它是有效的,但它有一个错误 我现在所拥有的: scrollview是它自己的委托。每次调用scrollView

简而言之,我需要知道scrollview何时停止滚动。所谓“停止滚动”,我指的是它不再移动,也不再被触摸的时刻

我一直在开发一个水平的UIScrollView子类(适用于iOS 4),其中包含选择选项卡。它的一个要求是在低于一定速度时停止滚动,以允许用户更快地交互。它还应该捕捉到选项卡的开头。换句话说,当用户释放scrollview且其速度较低时,它会捕捉到一个位置。我已经实现了这一点,它是有效的,但它有一个错误

我现在所拥有的:

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;
}
除以下两种情况外,这在大多数情况下都很有效: 1.当用户滚动而不松开手指,并在移动后以接近静止的时间松开时,它通常会按预期捕捉到其位置,但很多时候不会。通常需要几次尝试才能实现。释放时会出现时间(非常低)和/或距离(相当高)的奇数值,从而在scrollView几乎或完全静止的情况下产生一个高速值。 2.当用户点击scrollview停止移动时,scrollview似乎将
contentOffset
设置为其上一个位置。这种隐形传送会产生非常高的速度值。这可以通过检查前一个增量是否为currentDelta*-1来解决,但我更喜欢一个更稳定的解决方案

我尝试过使用
didendredelocing
,但当出现故障时,它不会被调用。这可能证实了它已经静止不动了。当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
@interface UIScrollView
{
布尔可以停止滚动;
浮动补偿;
float deltaOffset;//出于调试目的而记住
NSTIME间隔时间;
NSTimeInterval deltaTime;//出于调试目的而记住
浮子电流速度;
}
-(无效)停止移动;
-(无效)如有需要;
-(无效)刷新当前速度;
@结束
@实现选项卡滚动视图
-(id)init
{
self=[super init];
如果(自我)
{
self.delegate=self;
self.frame=CGRectMake(0.0f、0.0f、320.0f、40.0f);
self.backgroundColor=[UIColor grayColor];
浮动选项卡宽度=self.frame.size.width/3;
self.contentSize=CGSizeMake(100*tabWidth,40.0f);
对于(int i=0;i我找到了一个解决方案:

-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
我没有注意到之前的最后一点,
将减速
。当滚动视图在结束触摸时静止时,这是错误的。结合上述速度检查,我可以在它缓慢(未被触摸)或静止时捕捉

对于未执行任何捕捉但需要知道何时停止滚动的用户,将在滚动运动结束时调用
didendreging
。结合
didendraging
中的
willdregate
,您将知道滚动何时停止。

[Edit Answer] 这就是我所使用的-它处理所有的边缘情况。你需要一个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行代码:

本文中提到的委托方法对我没有帮助。我在
scrollViewDidScroll:
链接中找到了另一个检测最终滚动结束的答案o组合
ScrollViewDiEndDecreating
ScrollViewDiEndDraging:willDecreate
以在滚动完成时执行某些操作:

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

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

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

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

它涉及到“截取委托”来执行一些操作,然后将委托消息传递到预期的位置。这是必需的,因为在某些情况下,如果委托以编程方式移动,或不移动,或两者都移动,则会触发委托。因此,最好只查看下面的链接:


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

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

  • 当用户停止屏幕上的拖动手势时,会触发停止拖动。当速度非常慢时,此后将不再发生移动
  • 当用户以某种方式按下滚动视图时,会触发停止减速,用户触摸手指后滚动视图仍在移动,然后结束。此功能并不总是触发,当用户触摸手指后没有移动时,不会触发触发“停止拖动”上方的功能
  • 因此,总之,有必要将这两种功能结合起来,就像@WayneBurkett在他的回答中已经指出的那样

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

    您能否将
    didendraging
    与触摸事件结合起来,这样当用户触摸视图时,您就可以设置一个标志,并在他们移开手指时重置。然后,只要设置了标志,您就不会运行
    didendraging:
    下的相关逻辑。谢谢您的回答。但是,我不确定您提到的标志应该是什么关于。在我看来,您的意思是当scrollview被重新设置时,标志会显示出来
    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
        // ...
    }