2011-10-06 19 views
25

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.

+0

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

+0

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

Respuesta

30

he encontrado una solución:

-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 

no me di cuenta que el último bit antes, willDecelerate. Es falso cuando scrollView está parado al finalizar el toque. Combinado con el control de velocidad antes mencionado, puedo ajustar ambos cuando es lento (y no se toca) o cuando está parado.

Para cualquier persona que no realice ningún ajuste, pero que necesita saber cuándo se detuvo el desplazamiento, se llamará didEndDecelerating al final del movimiento de desplazamiento. Combinado con un cheque en willDecelerate en didEndDragging, sabrá cuando el desplazamiento se haya detenido.

+0

¿funciona también cuando el pagingEnabled de scrollView es Sí? aquí está mi publicación con respecto a mi problema. http://stackoverflow.com/questions/9337996/objective-c-uiscrollview-pagingenabled-when-next-page-enters-start-the-anima – SeongHo

+0

@SeongHo Estoy bastante seguro de que sí. Actualmente no puedo verificarlo, y no lo haré por bastante tiempo. – Aberrant

20

[Respuesta editada] Esto es lo que uso, maneja todas las cajas de borde. Necesita un ivar para mantener el estado, y como se muestra en los comentarios, hay otras maneras 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 muy simple utiliza el código anterior, de modo que cuando una persona interactúa con un ScrollView (incluyendo una vista Web), que inhibe el proceso de trabajo intensivo hasta que el usuario deja de interactuar con el ScrollView Y la ScrollView tiene dejó de desplazarse. Es como 50 líneas de código: ScrollWatcher

+2

Esto, buen señor, es la solución más limpia para el problema en cuestión, que he visto. Gracias. – thomax

+0

En respuesta a su edición, puede que le guste esto: https://github.com/lmirosevic/GBToolbox/commit/87be97a5eff702ef9ef0361fcb0539f36eb4984f Simplemente llame a 'ExecuteAfterScrolling (^ {NSLog (@" heavy work ");});' y no necesita meterse con los estados booleanos y dejar su código en blanco con las verificaciones condicionales. – lms

+0

@lms ¿Puedes explicar por qué funciona? Parece que el método se acaba de ejecutar en un NSTimer –

2

Los métodos de delegado mencionados en las respuestas de esta publicación no me sirvieron de nada. He encontrado otra respuesta que detectan final de desplazamiento final en scrollViewDidScroll: Link es here

+0

+1, esto me ayudó, gracias – NAZIK

18

Aquí es cómo combinar scrollViewDidEndDecelerating y scrollViewDidEndDragging:willDecelerate para realizar alguna operación cuando el desplazamiento ha terminado:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 
{ 
    [self stoppedScrolling]; 
} 

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView 
        willDecelerate:(BOOL)decelerate 
{ 
    if (!decelerate) { 
     [self stoppedScrolling]; 
    } 
} 

- (void)stoppedScrolling 
{ 
    // done, do whatever 
} 
1

I respondido a esta pregunta en mi blog que esboza cómo hacerlo sin problemas.

Implica 'interceptar al delegado' para hacer algunas cosas, luego pasar los mensajes de delegado a donde estaban destinados. Esto es necesario porque hay algunos escenarios en los que los fuegos de delegados si se movían mediante programación, o no, o ambos, y así de todos modos, la mejor manera de mirar justo en este enlace de abajo:

How to know when a UIScrollView is scrolling

Es un poco complicado , pero he puesto una receta allí y he explicado las partes en detalle.

Cuestiones relacionadas