我一直在开发一个水平UIScrollView子类(适用于iOS 4),其中包含选择标签。其中一个要求是,它会在低于某个速度的情况下停止滚动,以便更快地进行用户交互。它还应该捕捉到标签的开始位置。换句话说,当用户释放滚动视图并且其速度很慢时,它会捕捉到一个位置。我已经实现了这一点,并且它可以工作,但是有一个错误。
我的做法:
滚动视图是它自己的代理。在每次调用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;
}
这里是委托方法:
-(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. 当用户滚动时不松开手指,并在移动后几乎马上松开手指时,它通常会像预期的那样回到位置,但很多时候却没有。在释放时出现奇怪的时间值(非常低)和/或距离值(相当高),导致scrollView的实际静止或完全静止时速度值非常高。
2. 当用户点击scrollview停止其移动时,scrollview似乎会将contentOffset
设置为其之前的位置。这种瞬间移动会导致非常高的速度值。可以通过检查先前的增量是否为currentDelta * -1来解决此问题,但我更喜欢更稳定的解决方案。我尝试使用
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
与触摸事件结合起来,这样当用户触摸视图时,你设置一个标志并在他们移开手指时重置。然后只要标志被设置,就不会运行didEndDragging:
下的相关逻辑。 - FreeAsInBeer