31

Me gustaría poder mover una subvista dentro y fuera de la pantalla, como navegar entre imágenes en la versión de iPhone en la aplicación Fotos, así que si la subvista está a más de 1/2 de la pantalla cuando lo dejo ir con el dedo, debe animarse fuera de la pantalla, pero también debe admitir el deslizamiento, de modo que si la velocidad del deslizamiento/panorámica es lo suficientemente alta, debe animarse fuera de la pantalla aunque esté a menos de 1/2 de la pantalla.Animación de UIView basada en UIPanGestureRecognizer velocity

Mi idea era usar UIPanGestureRecognizer y luego probar la velocidad. Esto funciona, pero ¿cómo configuro una duración correcta de la animación para mover UIView en función de la ubicación actual de la vista y la velocidad de la panorámica para que parezca uniforme? Si configuro un valor fijo, la animación comienza a ser lenta o rápida en comparación con la velocidad de deslizamiento de mis dedos.

Respuesta

47

Los documentos dicen

La velocidad del gesto sartén, que se expresa en puntos por segundo. La velocidad se divide en componentes horizontales y verticales.

así que diría que, teniendo en cuenta que desea mover la vista xPoints (medida en pt) que dejarlo ir fuera de la pantalla, se puede calcular la duración de ese movimiento, así:

CGFloat xPoints = 320.0; 
CGFloat velocityX = [panRecognizer velocityInView:aView].x; 
NSTimeInterval duration = xPoints/velocityX; 
CGPoint offScreenCenter = moveView.center; 
offScreenCenter.x += xPoints; 
[UIView animateWithDuration:duration animations:^{ 
    moveView.center = offScreenCenter; 
}]; 

Es posible que desee utilizar + (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion en su lugar y probar diferentes UIViewAnimationOptions.

20

Una observación sobre la dependencia de la velocidad en UIPanGestureRecognizer: No conozco su experiencia, pero encontré que la velocidad generada por el sistema en el simulador no era demasiado útil. (Está bien en el dispositivo, pero es problemático en el simulador)

Si se desplaza rápidamente y se detiene bruscamente, espera y solo luego finaliza el gesto (por ejemplo, el usuario inicia un desliz, se da cuenta de que esto no era lo que querían) , así que se detienen y luego sueltan el dedo), la velocidad informada por velocityInView: en el estado UIGestureRecognizerStateEnded cuando se liberó su dedo parece ser la velocidad rápida antes de detenerse y esperar, mientras que la velocidad correcta en este ejemplo sería cero (o cerca cero). En resumen, la velocidad indicada es la que estaba justo antes del final de la sartén, pero no la velocidad al final de la sartén.

Terminé calculando la velocidad yo mismo, manualmente. (Parece tonto que esto sea necesario, pero no vi ninguna forma de evitarlo si realmente quería obtener la velocidad final de la sartén). En pocas palabras, cuando el estado es UIGestureRecognizerStateChanged Realizo un seguimiento de la actual y la anterior translationInView CGPoint, así como la hora, y luego usar esos valores cuando estaba en el UIGestureRecognizerStateEnded para calcular la velocidad final real. Funciona bastante bien

Aquí está mi código para calcular la velocidad. Resulta que no estoy usando la velocidad para calcular la velocidad de la animación, sino que la estoy usando para determinar si el usuario hizo una panorámica lo suficientemente rápida o la movió lo suficientemente rápido como para que la vista se moviera más allá de la mitad de la pantalla y así desencadenando la animación entre vistas, pero el concepto de calcular la velocidad final parece aplicable a esta pregunta. Aquí está el código:

- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture 
{ 
    static CGPoint lastTranslate; // the last value 
    static CGPoint prevTranslate; // the value before that one 
    static NSTimeInterval lastTime; 
    static NSTimeInterval prevTime; 

    CGPoint translate = [gesture translationInView:self.view]; 

    if (gesture.state == UIGestureRecognizerStateBegan) 
    { 
     lastTime = [NSDate timeIntervalSinceReferenceDate]; 
     lastTranslate = translate; 
     prevTime = lastTime; 
     prevTranslate = lastTranslate; 
    } 
    else if (gesture.state == UIGestureRecognizerStateChanged) 
    { 
     prevTime = lastTime; 
     prevTranslate = lastTranslate; 
     lastTime = [NSDate timeIntervalSinceReferenceDate]; 
     lastTranslate = translate; 

     [self moveSubviewsBy:translate]; 
    } 
    else if (gesture.state == UIGestureRecognizerStateEnded) 
    { 
     CGPoint swipeVelocity = CGPointZero; 

     NSTimeInterval seconds = [NSDate timeIntervalSinceReferenceDate] - prevTime; 
     if (seconds) 
     { 
      swipeVelocity = CGPointMake((translate.x - prevTranslate.x)/seconds, (translate.y - prevTranslate.y)/seconds); 
     } 

     float inertiaSeconds = 1.0; // let's calculate where that flick would take us this far in the future 
     CGPoint final = CGPointMake(translate.x + swipeVelocity.x * inertiaSeconds, translate.y + swipeVelocity.y * inertiaSeconds); 

     [self animateSubviewsUsing:final]; 
    } 
} 
+0

No funciona en iOS 6 porque la traducción final y la última traducción son las mismas en 'UIGestureRecognizerStateEnded', por lo que la velocidad final es siempre 0. – an0

+1

Gracias, ya veo. Es un problema de simulador. Disparé un informe de error a Apple. – an0

+0

He modificado la fuente (cambiando algunos nombres de variables) para evitar esta confusión. Este código funcionaba bien tal como estaba, pero es de esperar que los nuevos nombres de variable lo hagan un poco menos confuso. Gracias por informar el error a Apple. – Rob

1

En la mayoría de los casos el establecimiento de UIViewAnimationOptionBeginFromCurrentState opción para UIView animador es suficiente para hacer una animación continua sin problemas.

Cuestiones relacionadas