animationDidStop for group animation - iphone

I have a group animation but I can not detect when hits animationDidStop. example of my code:
[group setDelegate:self];
[view.layer addAnimation:group forKey:#"groupAnimation"];
any of you knows how I know when the group animation is done?

You need to also set the animationName property to match, and ensure that your delegate function is properly defined:
CAAnimationGroup *group = [CAAnimationGroup animation];
group.duration = 2.0f;
group.delegate = self;
[group setValue:#"groupAnimation" forKey:#"animationName"];
[group setAnimations:[NSArray arrayWithObjects:myAnimation, myOtherAnimation, nil]];
[view.layer addAnimation:group forKey:#"groupAnimation"];
.
.
.
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)finished
{
if (finished)
{
NSString *animationName = [animation valueForKey:#"animationName"];
if ([animationName isEqualToString:#"groupAnimation"])
{
// your groupAnimation has ended
}
}
}
Please note that with group animations, the delegates set on your component animations will be ignored.

I came up with a way to organize animation completion code that works really well. First, I defined a custom type for a block of code to run when an animation completes:
typedef void (^animationCompletionBlock)(void);
I define a constant that I use to add a custom key-value pair to my animation:
#define kAnimationCompletionBlock #"animationCompletionBlock"
Then, when I create an animation, if I want it to execute a block of code when it finishes, I add a custom property to my animation that contains the block of code I want to execute:
animationCompletionBlock theBlock;
theBlock = ^void(void)
{
someButton.enabled = TRUE;
NSLog(#"Animation complete");
//whatever other code you want to do on completion
}
[myAnimation setValue: theBlock forKey: kAnimationCompletionBlock];
I set the view controller to be the animation's delegate:
myAnimation.delegate = self
Finally, I write a general-purpose animation completion method that looks for an animation completion block attached to the animation, and executes it if it finds it:
/*
This method looks for a value added to the animation that just completed
with the key kAnimationCompletionBlock.
If it exists, it assumes it is a code block of type animationCompletionBlock,
and executes the code block.
This allows you to add a custom block of completion code to any animation or
animation group, rather than Having a big complicated switch statement in your
animationDidStop:finished: method with global animation ompletion code.
(Note that the system won't call the animationDidStop:finished method for
individual animations in an animation group - it will only call the
completion method for the entire group. Thus, if you want to run code after
part of an animation group completes, you have to set up a manual timer.)
*/
- (void)animationDidStop:(CAAnimation *)theAnimation
finished:(BOOL)flag
{
animationCompletionBlock theBlock =
[theAnimation valueForKey: kAnimationCompletionBlock];
if (theBlock)
theBlock();
}
In addition to being very clean, this approach also lets your animation completion code have access to local variables that are inside the scope where you define the block. That solves the problem of passing information to your completion method, which can be difficult.
You can see this technique in a working example program I posted to Github:
Core Animation demo on Github, including completion block code

I'm using this category for setting the completion like this:
[group setCompletionBlock:^{
}];
First CAAnimationGroup+Blocks.h:
#import <QuartzCore/QuartzCore.h>
#import <objc/runtime.h>
typedef void (^TIFAnimationGroupCompletionBlock)();
#interface CAAnimationGroup (Blocks)
- (void)setCompletionBlock:(TIFAnimationGroupCompletionBlock)handler;
#end
And CAAnimationGroup+Blocks.m:
#import "CAAnimationGroup+Blocks.h"
static char CAAnimationGroupBlockKey;
#implementation CAAnimationGroup (Blocks)
- (void)setCompletionBlock:(TIFAnimationGroupCompletionBlock)handler {
objc_setAssociatedObject(self, &CAAnimationGroupBlockKey, handler, OBJC_ASSOCIATION_COPY_NONATOMIC);
self.delegate = self;
}
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)finished
{
if (finished)
{
TIFAnimationGroupCompletionBlock handler = (TIFAnimationGroupCompletionBlock)objc_getAssociatedObject(self, &CAAnimationGroupBlockKey);
if (handler) {
handler();
}
}
}
#end

Related

Removing animation sprite frames from layer?

Let's say I have a character in a game and its class is like this.
#interface Player
{
CCSprite* stand;
CCAnimation* run;
}
-(void) playRunAction
{
// Create CCAnimate* object from CCAnimation object (run)
[self runAction:runAniate];
}
-(void) playStandAction
{
stand.visible = YES;
[self stopAllActions];
}
The player has ability to stand or run.
But one problem is, after playStandAction is called, stand animation is visible and running animation stopped, but one frame of running animation still there!
( Now you see 'stand sprite' AND 'one of running animation frame' together. )
How can I make running animation not visible?
P.s Can anyone throw me a better way of managing animation in one character? This is totally disaster as animations added.
-(void) playStandAction
{
//Make the animation object.visible = NO; here
stand.visible = YES;
[self stopAllActions];
}
and in
-(void) playRunAction
{
// Create CCAnimate* object from CCAnimation object (run)
//Make the animation object.visible = YES; here
stand.visible = NO;
[self runAction:runAniate];
}
Use method with parameter restoreOriginalFrame and pass it yes
I don't know which method you are calling for creating CCAnimate object...
Like this:
[CCAnimate actionWithAnimation:animation restoreOriginalFrame:YES]];
And don't call runAction on layer. I would prefer you to runAction on sprite itself...
You don't need to hide and show 2 different objects...
Hope this helps. :)

Can I pass a function name as an argument ?

I want to make this class dynamically perform functions on itself by passing different names to it. Is that possible ? Or rather: How is it possible ?
-(id)initWithMethod:(NSString*)method{
if ((self = [super init])){
[self method];
}
return self;
}
-(void) lowHealth {
CCSprite *blackScreen = [CCSprite spriteWithFile:#"blackscreen.png"];
blackScreen.anchorPoint = ccp(0,0);
[self addChild:blackScreen];
id fadeIn = [CCFadeIn actionWithDuration:1];
id fadeOut = [CCFadeOut actionWithDuration:1];
id fadeInAndOut = [CCRepeatForever actionWithAction:[CCSequence actions:fadeIn, fadeOut, nil]];
[blackScreen runAction:fadeInAndOut];
}
You should use performSelector and get the selector from your NSString using NSSelectorFromString:
[self performSelector:NSSelectorFromString(method)];
instead of [self method];
The standard way is using Selectors as mentioned in Matteo's answer.
You can also look at Objective-C Blocks. They are becoming very common in the CocoaTouch APIs and you can do some very slick things with them. The resulting architecture of your class is often easier to understand IMO.
For example this method from UIView
+ (void)animateWithDuration:(NSTimeInterval)duration
animations:(void (^)(void))animations
completion:(void (^)(BOOL finished))completion
Takes two block, one that is runs the code for the actual animation, and one to for the code after the animation is complete. You could call this with block variables or by writing code inline:
...animations:^{
// animation code
}
completion:^(BOOL finished) {
// completion code
}
The receiving method (in this case animateWithDuration:...) would simply call these blocks at some point like so:
animations();

iPhone - Catching the end of a grouped animation

I animate 2 views, each one with its CAAnimationGroup that contains 2 CAAnimations. They are launched at the same time (if computing time is negligible), and have the same duration.
How may I do to know when both grouped animation is finished.
I put the - (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag delegate method, but... What may I test ? Sounds simple, but I don't see the way of doing this.
You can use two variables to track whether the animations have completed:
BOOL firstAnimFinished;
BOOL secondAnimFinished;
then in your animationDidStop delegate you check which animation is calling the method and set the flags appropriately. The catch is that you'll need to add a key to identify the animations when they call the delegate (the animations you created will not be the ones calling the delegate, which is a subject for another question/rant). For example:
// when you create the animations
[firstAnmim setValue: #"FirstAnim" ForKey: #"Name"];
[secondAnmim setValue: #"SecondAnim" ForKey: #"Name"];
// Your delegate
- (void)animationDidStop:(CAAnimation*)theAnimation finished:(BOOL)flag {
NSString* name = [theAnimation valueForKey: #"Name"];
if ([name isEqualToString: #"FirstAnim"]) {
firstAnimFinished = YES;
} else if ([name isEqualToString: #"SecondAnim"]) {
secondAnimFinished = YES;
}
if (firstAnimFinished && secondAnimFinished) {
// ALL DONE...
}
}

How to identify CAAnimation in delegate and release controller?

I have seen How to identify CAAnimation within the animationDidStop delegate?, this is an addition to it.
I'm unable to get this working properly. I have an animation, and I'd like to release the controller that it was run in after the end of the animation.
Example: The controller translates from right -> left then releases itself.
Defining the animation:
NSValue *end = [NSValue valueWithCGPoint:CGPointMake(800, self.view.center.y)];
NSValue *start = [NSValue valueWithCGPoint:self.view.center];
CABasicAnimation *moveAnimation;
moveAnimation = [CABasicAnimation animationWithKeyPath:#"position"];
moveAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
moveAnimation.duration = 0.45f;
moveAnimation.fromValue = start;
moveAnimation.toValue = end;
// actually set the position
[self.view.layer setPosition:[end CGPointValue]];
moveAnimation.delegate = self;
moveAnimation.removedOnCompletion = NO;
[self.view.layer addAnimation:moveAnimation forKey:MOVING_OUT];
Inside the delegate method:
- (void) animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
CAAnimation *check = [self.view.layer animationForKey:MOVING_OUT];
if (theAnimation == check)
{
//[check release];
[self release];
}
}
If I leave this code as-is, my controller doesn't get dealloc'd (due to the retain call by the animation).
If I run [check release], I get the message sent to deallocated instance.
Does anyone know what's wrong?
Is there another way to identify a CAAnimation in the animationDidStop delegate WITHOUT specifying removedOnCompletion = NO?
EDIT:
Forgot to mention. By not specifying that removedOnCompletion = NO, animationForKey: will return NULL. Hence I'm unable to identify the animation.
Thanks!
I think the eventual reason is CAAnimation.delegate is a retain property (very strange oops!).
The header file definition is:
/* The delegate of the animation. This object is retained for the
* lifetime of the animation object. Defaults to nil. See below for the
* supported delegate methods. */
#property(retain) id delegate;
To let self get release, the animation must be removed from layer, like:
[self.view.layer removeAnimationForKey:#THE_ANIMATION_KEY];
It's not clear what your problem is here, but it may help you to know that CAAnimation instances are generic KVO containers, so you can add custom info to them:
[myAnimation setValue: #"check" forKey: #"name"];
You can then check against that:
if ([[theAnimation valueForKey: #"name"] isEqual: #"check"])
// ...
Does that help?

Resetting a view

When the user navigates to a different view and returns back to the original view I would like everything to be reset as though they were coming to the view for the first time.
I was able to stop the audio from playing when they leave, but not an animation method.
how would I be able to do this?
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[audioIntro stop];
[mainAudio stop];
}
here's the animation code:
- (void) firstAnimation {
if (playSoundButton.enabled = NO || audioIntro.playing) {
return;
}
UIViewAnimationOptions options = UIViewAnimationOptionCurveLinear;
myImage.hidden = NO;
myImage.alpha = 1.0;
[UIView animateWithDuration:1.0 delay:12.0 options:options animations:^
{
setAnimationBeginsFromCurrentState:YES;
tapImage.alpha = 0.0;
}
completion:^(BOOL finished){
[self performSelector:#selector(secondAnimation) withObject:nil afterDelay:0.2];
}
];
}
thanks for any help.
It seems a bit hacky - but I would try this:
Use the "old style" animation - i.e. not with blocks - use "beginAnimation", setting an animationID for your animation.
In you viewWillDisappear method, call "beginAnimation" again, with the same animationID, but this time:
a. Set the animation duration to ZERO
b. Animate "to" the location where you want the stuff to reset to.
Set some sort of kill-flag in the viewWillDisappear method
Make the completion function look for the "kill flag" - and do not evoke the second animation inside your "secondAnimation" flag if the selector if the "kill flag" is set.
(I am a little unclear - if the "finished" flag would suffice for the "kill-flag" - not having seen your entire code.