Короче говоря, мне нужно точно знать, когда прокрутка просмотрела прокрутку. Под "остановленной прокруткой" я подразумеваю момент, когда он больше не движется и не трогается.
Я работал над горизонтальным подклассом 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;
}
Вот методы делегата:
-(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 устанавливает contentOffset
в его предыдущее место. Эта телепортация приводит к очень высокому значению скорости. Это можно было бы исправить, проверяя, является ли предыдущая дельта текущей Delta * -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:
часто происходит во время прокрутки, и проверка скорости ненадежна из-за этого нечетного "прыжка", который устанавливает скорость на что-то случайное.