¿Cómo identificar CAAnimation dentro del delegado animationDidStop?

102

Tuve un problema en el que tenía una serie de secuencias CATransition / CAAnimation superpuestas, todas las cuales necesitaba realizar operaciones personalizadas cuando las animaciones se detenían, pero solo quería un controlador delegado para animationDidStop.

Sin embargo, tuve un problema, no parecía haber una forma de identificar de forma única cada CATransition / CAAnimation en el delegado animationDidStop.

Resolví este problema a través del sistema clave / valor expuesto como parte de CAAnimation.

Cuando comiences tu animación, usa el método setValue en CATransition / CAAnimation para configurar tus identificadores y valores para usar cuando animationDidStop se active:

-(void)volumeControlFadeToOrange
{   
    CATransition* volumeControlAnimation = [CATransition animation];
    [volumeControlAnimation setType:kCATransitionFade];
    [volumeControlAnimation setSubtype:kCATransitionFromTop];
    [volumeControlAnimation setDelegate:self];
    [volumeControlLevel setBackgroundImage:[UIImage imageNamed:@"SpecialVolume1.png"] forState:UIControlStateNormal];
    volumeControlLevel.enabled = true;
    [volumeControlAnimation setDuration:0.7];
    [volumeControlAnimation setValue:@"Special1" forKey:@"MyAnimationType"];
    [[volumeControlLevel layer] addAnimation:volumeControlAnimation forKey:nil];    
}

- (void)throbUp
{
    doThrobUp = true;

    CATransition *animation = [CATransition animation]; 
    [animation setType:kCATransitionFade];
    [animation setSubtype:kCATransitionFromTop];
    [animation setDelegate:self];
    [hearingAidHalo setBackgroundImage:[UIImage imageNamed:@"m13_grayglow.png"] forState:UIControlStateNormal];
    [animation setDuration:2.0];
    [animation setValue:@"Throb" forKey:@"MyAnimationType"];
    [[hearingAidHalo layer] addAnimation:animation forKey:nil];
}

En su delegado animationDidStop:

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag{

    NSString* value = [theAnimation valueForKey:@"MyAnimationType"];
    if ([value isEqualToString:@"Throb"])
    {
       //... Your code here ...
       return;
    }


    if ([value isEqualToString:@"Special1"])
    {
       //... Your code here ...
       return;
    }

    //Add any future keyed animation operations when the animations are stopped.
 }

El otro aspecto de esto es que le permite mantener el estado en el sistema de emparejamiento de valor clave en lugar de tener que almacenarlo en su clase delegada. Cuanto menos código, mejor.

Asegúrese de consultar la Referencia de Apple sobre codificación de pares de valores clave .

¿Existen mejores técnicas para la identificación de CAAnimation / CATransition en el delegado animationDidStop?

Gracias, - Batgar

Batgar
fuente
4
Batgar, cuando busqué en Google "iphone animationDidStopidentify", el primer resultado fue tu publicación, sugiriendo el uso de un valor clave para identificar la animación. Justo lo que necesitaba, gracias. Rudi
rudifa
1
Tenga en cuenta que CAAnimation's delegatees fuerte, por lo que puede que tenga que configurarlo para nilevitar retener ciclos!
Iulian Onofrei

Respuestas:

92

La técnica de Batgar es demasiado complicada. ¿Por qué no aprovechar el parámetro forKey en addAnimation? Estaba destinado a este mismo propósito. Simplemente saque la llamada a setValue y mueva la cadena de teclas a la llamada addAnimation. Por ejemplo:

[[hearingAidHalo layer] addAnimation:animation forKey:@"Throb"];

Luego, en su devolución de llamada animationDidStop, puede hacer algo como:

if (theAnimation == [[hearingAidHalo layer] animationForKey:@"Throb"]) ...
vocaro
fuente
Me gustaría mencionar que el uso de lo anterior INCREMENTA EL RECUENTO DE RETENCIÓN! Tenga cuidado. Es decir, animationForKey: incrementa el recuento de retención de su objeto CAAnimation.
mmilo
1
@mmilo Eso no es muy sorprendente, ¿verdad? Al agregar una animación a una capa, la capa es propietaria de la animación, por lo que, por supuesto, se incrementa el recuento de retención de la animación.
GorillaPatch
16
No funciona: cuando se llama al selector de parada, la animación ya no existe. Obtienes una referencia nula.
Adam
4
Eso es un mal uso del parámetro forKey: y no es necesario. Lo que Batgar estaba haciendo es exactamente correcto: la codificación de valores clave le permite adjuntar cualquier dato arbitrario a su animación, para que pueda identificarlo fácilmente.
Matt
7
Adam, consulte la respuesta de Jimt a continuación: debe configurarlo anim.removedOnCompletion = NO;para que aún exista cuando -animationDidStop:finished:se llame.
Yang Meyer
46

Se me ocurrió una forma aún mejor de hacer el código de finalización para CAAnimations:

Creé un typedef para un bloque:

typedef void (^animationCompletionBlock)(void);

Y una clave que uso para agregar un bloque a una animación:

#define kAnimationCompletionBlock @"animationCompletionBlock"

Luego, si quiero ejecutar el código de finalización de la animación después de que finalice una CAAnimation, me establezco como delegado de la animación y agrego un bloque de código a la animación usando setValue: forKey:

animationCompletionBlock theBlock = ^void(void)
{
  //Code to execute after the animation completes goes here    
};
[theAnimation setValue: theBlock forKey: kAnimationCompletionBlock];

Luego, implemento un método animationDidStop: finalizado:, que busca un bloque en la clave especificada y lo ejecuta si lo encuentra:

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
  animationCompletionBlock theBlock = [theAnimation valueForKey: kAnimationCompletionBlock];
  if (theBlock)
    theBlock();
}

La belleza de este enfoque es que puede escribir el código de limpieza en el mismo lugar donde crea el objeto de animación. Mejor aún, dado que el código es un bloque, tiene acceso a las variables locales en el ámbito adjunto en el que está definido. No tiene que meterse con la configuración de diccionarios de userInfo u otras tonterías, y no tiene que escribir un método animationDidStop: finalizado: cada vez mayor que se vuelve cada vez más complejo a medida que agrega diferentes tipos de animaciones.

A decir verdad, CAAnimation debería tener una propiedad de bloque de finalización incorporada y soporte del sistema para llamarlo automáticamente si se especifica uno. Sin embargo, el código anterior le brinda la misma funcionalidad con solo unas pocas líneas de código adicional.

Duncan C
fuente
7
Alguien también armó una categoría en CAAnimation para esto: github.com/xissburg/CAAnimationBlocks
Jay Peyer
Esto no parece correcto. Muy a menudo, obtengo un EXEC_Err justo después de que theBlock();se invoca, y creo que se debe al hecho de que se destruyó el alcance del bloque.
mahboudz
He estado usando el bloque por un tiempo y funciona MUCHO mejor que el terrible enfoque "oficial" de Apple.
Adam
3
Estoy bastante seguro de que necesitaría [copiar en bloque] ese bloque antes de establecerlo como valor para una propiedad.
Fiona Hopkins
1
No, no es necesario copiar el bloque.
Duncan C
33

El segundo enfoque solo funcionará si estableces explícitamente que tu animación no se elimine al finalizar antes de ejecutarla:

CAAnimation *anim = ...
anim.removedOnCompletion = NO;

Si no lo hace, su animación se eliminará antes de que se complete y la devolución de llamada no la encontrará en el diccionario.

Jimt
fuente
10
Esto debería ser un comentario, no una respuesta.
Hasta el
2
Me pregunto si es necesario eliminarlo explícitamente después con removeAnimationForKey.
bompf
Realmente depende de lo que quieras hacer. Puede eliminarlo si es necesario o dejarlo porque quiere hacer algo más en conjunto.
applejack42
31

¡Todas las demás respuestas son demasiado complicadas! ¿Por qué no agrega su propia clave para identificar la animación?

Esta solución es muy fácil, todo lo que necesita es agregar su propia clave a la animación (animationID en este ejemplo)

Inserte esta línea para identificar animation1 :

[myAnimation1 setValue:@"animation1" forKey:@"animationID"];

y esto para identificar animation2 :

[myAnimation2 setValue:@"animation2" forKey:@"animationID"];

Pruébelo así:

- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag
{
    if([[animation valueForKey:@"animationID"] isEqual:@"animation1"]) {
    //animation is animation1

    } else if([[animation valueForKey:@"animationID"] isEqual:@"animation2"]) {
    //animation is animation2

    } else {
    //something else
    }
}

No requiere ninguna variable de instancia :

Tibidabo
fuente
[animation valueForKey:@"animationID"]
Obtengo
14

Para hacer explícito lo que está implícito desde arriba (y lo que me trajo aquí después de algunas horas desperdiciadas): no espere ver el objeto de animación original que asignó.

 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)flag 

cuando termina la animación, porque [CALayer addAnimation:forKey:]hace una copia de tu animación.

En lo que puede confiar es en que los valores con clave que le dio a su objeto de animación todavía están allí con un valor equivalente (pero no necesariamente equivalencia de puntero) en el objeto de animación de réplica pasado con el animationDidStop:finished:mensaje. Como se mencionó anteriormente, use KVC y obtendrá un amplio margen para almacenar y recuperar el estado.

primero
fuente
1
+1 ¡Esta es la mejor solución! Puede establecer el 'nombre' de la animación con [animation setValue:@"myanim" forKey:@"name"]e incluso puede configurar la capa que se está animando usando [animation setValue:layer forKey:@"layer"]. Luego, estos valores se pueden recuperar dentro de los métodos delegados.
trojanfoe
valueForKey:vuelve nilpor mí, ¿alguna idea de por qué?
Iulian Onofrei
@IulianOnofrei compruebe que su animación no fue desplazada por otra animación para la misma propiedad; puede suceder como un efecto secundario inesperado.
t0rst
@ t0rst, Lo siento, teniendo varias animaciones y usando copiar y pegar, estaba estableciendo diferentes valores en la misma variable de animación.
Iulian Onofrei
2

Puedo ver la mayoría de las respuestas objc. Haré una para swift 2.3 según la mejor respuesta anterior.

Para empezar, será bueno almacenar todas esas claves en una estructura privada para que sea seguro de tipos y cambiarlo en el futuro no le traerá errores molestos solo porque olvidó cambiarlo en todas partes del código:

private struct AnimationKeys {
    static let animationType = "animationType"
    static let volumeControl = "volumeControl"
    static let throbUp = "throbUp"
}

Como puede ver he cambiado los nombres de las variables / animaciones para que quede más claro. Ahora establezca estas claves cuando se crea la animación.

volumeControlAnimation.setValue(AnimationKeys.volumeControl, forKey: AnimationKeys.animationType)

(...)

throbUpAnimation.setValue(AnimationKeys.throbUp, forKey: AnimationKeys.animationType)

Luego, finalmente manejando al delegado para cuando la animación se detenga.

override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
    if let value = anim.valueForKey(AnimationKeys.animationType) as? String {
        if value == AnimationKeys.volumeControl {
            //Do volumeControl handling
        } else if value == AnimationKeys.throbUp {
            //Do throbUp handling
        }
    }
}
apinho
fuente
0

En mi humilde opinión, usar el valor clave de Apple es la forma elegante de hacer esto: está específicamente diseñado para permitir agregar datos específicos de la aplicación a los objetos.

Otra posibilidad mucho menos elegante es almacenar referencias a sus objetos de animación y hacer una comparación de punteros para identificarlos.

Teemu Kurppa
fuente
Esto nunca funcionará; no puede hacer equivalencia de puntero, porque Apple cambia el puntero.
Adam
0

Para verificar si 2 objetos CABasicAnimation son la misma animación, uso la función keyPath para hacer exactamente eso.

if ([animationA keyPath] == [animationB keyPath])

  • No es necesario configurar KeyPath para CABasicAnimation ya que ya no se animará
Sirisilp Kongsilp
fuente
la pregunta se relaciona con devoluciones de llamada de delegado, y keyPath no es un método en CAAnimation
Max MacLeod
0

Me gusta usar setValue:forKey: para mantener una referencia de la vista que estoy animando, es más seguro que intentar identificar de manera única la animación basada en la ID porque el mismo tipo de animación se puede agregar a diferentes capas.

Estos dos son equivalentes:

[UIView animateWithDuration: 0.35
                 animations: ^{
                     myLabel.alpha = 0;
                 } completion: ^(BOOL finished) {
                     [myLabel removeFromSuperview];
                 }];

Con este:

CABasicAnimation *fadeOut = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeOut.fromValue = @([myLabel.layer opacity]);
fadeOut.toValue = @(0.0);
fadeOut.duration = 0.35;
fadeOut.fillMode = kCAFillModeForwards;
[fadeOut setValue:myLabel forKey:@"item"]; // Keep a reference to myLabel
fadeOut.delegate = self;
[myLabel.layer addAnimation:fadeOut forKey:@"fadeOut"];
myLabel.layer.opacity = 0;

y en el método delegado:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    id item = [anim valueForKey:@"item"];

    if ([item isKindOfClass:[UIView class]])
    {
        // Here you can identify the view by tag, class type 
        // or simply compare it with a member object

        [(UIView *)item removeFromSuperview];
    }
}
Andrei Marincas
fuente
0

Xcode 9 Swift 4.0

Puede usar valores clave para relacionar una animación que agregó a la animación devuelta en el método delegado animationDidStop.

Declare que un diccionario contiene todas las animaciones activas y terminaciones relacionadas:

 var animationId: Int = 1
 var animating: [Int : () -> Void] = [:]

Cuando agregue su animación, establezca una clave para ella:

moveAndResizeAnimation.setValue(animationId, forKey: "CompletionId")
animating[animationId] = {
    print("completion of moveAndResize animation")
}
animationId += 1    

En animationDidStop, ocurre la magia:

    let animObject = anim as NSObject
    if let keyValue = animObject.value(forKey: "CompletionId") as? Int {
        if let completion = animating.removeValue(forKey: keyValue) {
            completion()
        }
    }
Eng Yew
fuente