¿Cómo identificar CAAnimation dentro del delegado animationDidStop?

Tuve un problema en el que tenía una serie de secuencias de CATransición / CAAnimación superpuestas, todas las cuales necesitaba para 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 única de identificar cada CATransición / CAAnimación en el delegado animationDidStop.

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

Cuando inicie la animación, use el método setValue en CATransition / CAAnimation para establecer sus identificadores y valores para utilizar cuando se activa animationDidStop:

-(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 animaciónDidStop delegado:

 - (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 de delegado. Cuanto menos código, mejor.

Asegúrese de consultar la referencia de Apple en la encoding de pares de valores clave .

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

Gracias, –Batgar

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

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

Luego, en su callback animationDidStop, puede hacer algo como:

 if (theAnimation == [[hearingAidHalo layer] animationForKey:@"Throb"]) ... 

Acabo de presentar 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 utilizo para agregar un bloque a una animación:

 #define kAnimationCompletionBlock @"animationCompletionBlock" 

Luego, si deseo ejecutar el código de finalización de la animación luego de que termine CAAnimation, me configuro como el 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: finished: que busca un bloque en la clave especificada y lo ejecuta si se 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 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 de animación en constante crecimientoDidStop: finished: que se vuelve más y más complejo a medida que agrega diferentes tipos de animaciones.

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

El segundo enfoque solo funcionará si configura explícitamente su animación para que no se elimine al completarla antes de ejecutarla:

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

Si no lo hace, su animación se eliminará antes cuando se complete, y la callback no la encontrará en el diccionario.

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

Esta solución es muy sencilla. Todo lo que necesita es agregar su propia clave a la animación (animationID en este ejemplo)

Inserta esta línea para identificar animation1 :

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

y esto para identificar animation2 :

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

Pruébalo 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 :

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

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

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

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

Puedo ver respuestas principalmente objc. Haré una para 2.3 rápido basado en la mejor respuesta anterior.

Para empezar, será bueno almacenar todas esas claves en una estructura privada para que sea segura y cambiarla en el futuro no le traerá molestos errores simplemente porque olvidó cambiarla en todas partes en el 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 por lo que es más claro. Ahora configura estas teclas 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 se detenga la animación

 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 } } } 

En mi humilde opinión, el uso de la clave-valor de Apple es la forma elegante de hacerlo: está específicamente destinado a permitir la adición de 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.

Para mí comprobar si 2 objetos CABasicAnimation son la misma animación, utilizo la función keyPath para hacer exactamente eso.

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

  • No es necesario configurar KeyPath para CABasicAnimation, ya que dejará de animar

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 de delegado animationDidStop.

Declara un diccionario para contener todas las animaciones activas y terminaciones relacionadas:

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

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

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

En animationDidStop, la magia sucede:

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

Me gusta usar setValue:forKey : para mantener una referencia de la vista que estoy animando, es más seguro que tratar de identificar de manera única la animación basada en ID porque se puede agregar el mismo tipo de animación 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 de 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]; } }