objective c - example - ¿Cómo saber exactamente cuándo se ha detenido el desplazamiento de un UIScrollView?
scrollview xcode 9 (5)
Aquí se explica cómo combinar scrollViewDidEndDecelerating
y scrollViewDidEndDragging:willDecelerate
para realizar alguna operación cuando el desplazamiento haya finalizado:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self stoppedScrolling];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
willDecelerate:(BOOL)decelerate
{
if (!decelerate) {
[self stoppedScrolling];
}
}
- (void)stoppedScrolling
{
// done, do whatever
}
En resumen, necesito saber exactamente cuándo se detuvo el desplazamiento en la vista de desplazamiento. Por "detener el desplazamiento", me refiero al momento en que ya no se mueve y no se toca.
He estado trabajando en una subclase de UIScrollView horizontal (para iOS 4) con pestañas de selección en ella. Uno de sus requisitos es que deja de desplazarse por debajo de una cierta velocidad para permitir la interacción del usuario más rápidamente. También debería ajustarse al inicio de una pestaña. En otras palabras, cuando el usuario libera la vista de desplazamiento y su velocidad es baja, se ajusta a una posición. He implementado esto y funciona, pero hay un error en ello.
Lo que tengo ahora:
El scrollview es su propio delegado. en cada llamada a scrollViewDidScroll :, actualiza 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 romper 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;
}
Aquí están 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];
}
Esto funciona bien la mayor parte del tiempo, excepto en dos escenarios: 1. Cuando el usuario se desplaza sin soltar su dedo y se desplaza cerca de un tiempo estacionario justo después de moverse, a menudo se ajusta a su posición como se supone que debe hacerlo, pero muchas veces, no lo hace. Por lo general, toma algunos intentos para que esto suceda. Los valores impares para el tiempo (muy bajo) y / o la distancia (bastante alto) aparecen en la versión, lo que provoca un valor de alta velocidad, mientras que el scrollView es, en realidad, casi estacionario. 2. Cuando el usuario toca la vista de desplazamiento para detener su movimiento, parece que la vista de desplazamiento establece el contentOffset
en su lugar 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 se produce el problema didEndDecelerating
, no se llama. Esto probablemente confirma que ya está estacionario. Parece que no hay un método de delegado que se llame cuando la vista de desplazamiento dejó de moverse por completo.
Si desea ver la falla, vea aquí un código para llenar la vista de desplazamiento 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 cuándo la vista de desplazamiento dejó de desplazarse? didEndDecelerating:
no recibe llamadas cuando lo sueltas de forma estacionaria, didEndDragging:
ocurre mucho durante el desplazamiento y la verificación de la velocidad no es confiable debido a este extraño "salto" que establece la velocidad en algo aleatorio.
Encontré una solución:
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
No noté ese último bit antes, se willDecelerate
. Es falso cuando el scrollView está estacionario cuando finaliza el toque. Combinado con el control de velocidad mencionado anteriormente, puedo ajustar tanto cuando es lento (y no se toca) como cuando está parado.
Para cualquier persona que no haga ningún ajuste, pero debe saber cuándo se detuvo el desplazamiento, se didEndDecelerating
al final del movimiento de desplazamiento. Combinado con una comprobación de willDecelerate
in didEndDragging
, sabrá cuándo se ha detenido el desplazamiento.
Los métodos de delegado mencionados en este post, las respuestas no me ayudaron. Encontré otra respuesta que detecta el final del desplazamiento final en scrollViewDidScroll:
enlace está here
Respondí esta pregunta en mi blog, que describe cómo hacerlo sin problemas.
Se trata de "interceptar al delegado" para hacer algunas cosas, y luego pasar los mensajes del delegado a donde fueron destinados. Esto es necesario porque hay algunos escenarios en los que el delegado se dispara si se movió mediante programación, o no, o ambos, y de todos modos, es mejor mirar este enlace a continuación:
Cómo saber cuando un UIScrollView se está desplazando
Es un poco complicado, pero he puesto una receta allí y he explicado las partes en detalle.
[Respuesta editada] Esto es lo que uso - maneja todos los casos de borde. Necesita un ivar para mantener el estado, y como se muestra en los comentarios, hay otras formas de manejar esto.
- (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");
}
Creé un proyecto realmente simple que utiliza el código anterior, de modo que cuando una persona interactúa con un scrollView (incluyendo un WebView), inhibe el trabajo intensivo del proceso hasta que el usuario deja de interactuar con el scrollView Y la scrollview ha dejado de desplazarse. Es como 50 líneas de código: ScrollWatcher