En resumen, necesito saber exactamente cuándo la vista de desplazamiento dejó de desplazarse. Por 'parado de desplazamiento', me refiero al momento en el que ya no se mueve ni se toca.¿Cómo saber exactamente cuándo se ha detenido el desplazamiento de UIScrollView?
He estado trabajando en una subclase horizontal UIScrollView (para iOS 4) con pestañas de selección en ella. Uno de sus requisitos es que deje de desplazarse por debajo de cierta velocidad para permitir la interacción del usuario más rápidamente. También debe ajustarse al comienzo de una pestaña. En otras palabras, cuando el usuario suelta la vista de desplazamiento y su velocidad es baja, se ajusta a una posición. Lo he implementado y funciona, pero hay un error en él.
lo que tengo ahora:
El ScrollView es su propio delegado. en cada llamada a scrollViewDidScroll :, refresca sus variables relacionadas con la velocidad:
-(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);
}
luego procede a ajustar si es necesario:
-(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;
}
Éstos son los métodos de delegado:
-(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];
}
Este funciona bien la mayor parte del tiempo, excepto en dos escenarios: 1. Cuando el usuario se desplaza sin soltar su dedo y lo suelta en un momento casi inmóvil justo después de moverse, a menudo se ajusta a su posición como se supone que debe, pero muchas veces, no lo hace. Por lo general, se requieren algunos intentos para que suceda. Los valores impares para el tiempo (muy bajo) y/o la distancia (bastante alta) aparecen en el lanzamiento, causando un valor de alta velocidad, mientras que el scrollView es, en realidad, casi o completamente estacionario. 2. Cuando el usuario toca la vista de desplazamiento para detener su movimiento, parece que la vista de desplazamiento establece contentOffset
en su punto anterior. Esta teletransportación da como resultado un valor de velocidad muy alta. Esto podría solucionarse verificando si el delta anterior es currentDelta * -1, pero preferiría una solución más estable.
He intentado usar didEndDecelerating
, pero cuando ocurre una falla, no se llama. Esto probablemente confirma que ya está estacionario. Parece que no hay ningún método de delegado que se llame cuando la vista de desplazamiento dejó de moverse por completo.
Si desea ver el fallo mismo, aquí hay un código para llenar el ScrollView con pestañas:
@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
Una versión más corta de esta pregunta: ¿cómo saber cuando el ScrollView deje de moverse? didEndDecelerating:
no recibe una llamada cuando lo suelta estacionario, didEndDragging:
sucede mucho durante el desplazamiento y la comprobación de la velocidad no es confiable debido a este extraño "salto" que establece la velocidad a algo aleatorio.
Puede combinar el 'didEndDragging' con un evento táctil, de modo que cuando el usuario toque la vista, establezca un indicador y lo restablezca cuando elimine su dedo. Entonces, siempre que se establezca la bandera, no ejecuta la lógica relacionada bajo 'didEndDragging:'. – FreeAsInBeer
Gracias por su respuesta. Sin embargo, no estoy seguro de qué se trata la bandera que mencionas. Me parece que te refieres a que la bandera se muestra cuando la vista de desplazamiento se ha lanzado una vez, pero no puedo entender cómo sería útil, teniendo en cuenta que el usuario suelta la vista de desplazamiento varias veces mientras se desplaza, o solo en un punto estacionario sin tener levantó su dedo. – Aberrant