How to identify CAAnimation within the animationDidStop delegate?

Viewed 52185

I had a problem where I had a series of overlapping CATransition / CAAnimation sequences, all of which I needed to perform custom operations when the animations stopped, but I only wanted one delegate handler for animationDidStop.

However, I had a problem, there didn't appear to be a way to uniquely identify each CATransition / CAAnimation in the animationDidStop delegate.

I solved this problem via the key / value system exposed as part of CAAnimation.

When you start your animation use the setValue method on the CATransition / CAAnimation to set your identifiers and values to use when animationDidStop fires:

-(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];
}

In your animationDidStop delegate:

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

The other aspect of this is that it allows you to keep state in the key value pairing system instead of having to store it in your delegate class. The less code, the better.

Be sure to check out the Apple Reference on Key Value Pair Coding.

Are there better techniques for CAAnimation / CATransition identification in the animationDidStop delegate?

Thanks, --Batgar

10 Answers

To make explicit what's implied from above (and what brought me here after a few wasted hours): don't expect to see the original animation object that you allocated passed back to you by

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

when the animation finishes, because [CALayer addAnimation:forKey:] makes a copy of your animation.

What you can rely on, is that the keyed values you gave to your animation object are still there with equivalent value (but not necessarily pointer equivalence) in the replica animation object passed with the animationDidStop:finished: message. As mentioned above, use KVC and you get ample scope to store and retrieve state.

Xcode 9 Swift 4.0

You can use Key Values to relate an animation you added to the animation returned in animationDidStop delegate method.

Declare a dictionary to contain all active animations and related completions:

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

When you add your animation, set a key for it:

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

In animationDidStop, the magic happens:

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

IMHO using Apple's key-value is the elegant way of doing this: it's specifically meant to allow adding application specific data to objects.

Other much less elegant possibility is to store references to your animation objects and do a pointer comparision to identify them.

Related