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
Batgar's technique is too complicated. Why not take advantage of the forKey parameter in addAnimation? It was intended for this very purpose. Just take out the call to setValue and move the key string to the addAnimation call. For example:
[[hearingAidHalo layer] addAnimation:animation forKey:@"Throb"];
Then, in your animationDidStop callback, you can do something like:
if (theAnimation == [[hearingAidHalo layer] animationForKey:@"Throb"]) ...
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()
}
}
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.
I just came up with an even better way to do completion code for CAAnimations:
I created a typedef for a block:
typedef void (^animationCompletionBlock)(void);
And a key that I use to add a block to an animation:
#define kAnimationCompletionBlock @"animationCompletionBlock"
Then, if I want to run animation completion code after a CAAnimation finishes, I set myself as the delegate of the animation, and add a block of code to the animation using setValue:forKey:
animationCompletionBlock theBlock = ^void(void)
{
//Code to execute after the animation completes goes here
};
[theAnimation setValue: theBlock forKey: kAnimationCompletionBlock];
Then, I implement an animationDidStop:finished: method, that checks for a block at the specified key and executes it if found:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
animationCompletionBlock theBlock = [theAnimation valueForKey: kAnimationCompletionBlock];
if (theBlock)
theBlock();
}
The beauty of this approach is that you can write the cleanup code in the same place where you create the animation object. Better still, since the code is a block, it has access to local variables in the enclosing scope in which it's defined. You don't have to mess with setting up userInfo dictionaries or other such nonsense, and don't have to write an ever-growing animationDidStop:finished: method that gets more and more complex as you add different kinds of animations.
Truth be told, CAAnimation should have a completion block property built into it, and system support for calling it automatically if one is specified. However, the above code gives you that same functionality with only a few lines of extra code.