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