How to identify CAAnimation in delegate and release controller? - iphone

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?

Related

animationDidStop for group animation

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

In App Purchase/View Controller Crash: Message sent to deallocated instance

I have a button on my view controller that presents my In App Purchases store.
storeSinglePlayer *ssp = [[storeSinglePlayer alloc] initWithNibName:#"storeSinglePlayer" bundle:nil];
//Animation Code
CATransition* transition = [CATransition animation];
transition.duration = 0.5;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromTop;
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[self.navigationController pushViewController:ssp animated:NO];
The navigation controller successfully pushes this store on the screen. The store has a back button which executes the following code:
[self.request cancel];
self.request.delegate = nil;
self.request = nil;
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
CATransition* transition = [CATransition animation];
transition.duration = 0.5;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromBottom;
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[self.navigationController popViewControllerAnimated:NO];
This executes successfully as well. But if I now click the button to show the store view controller again, I get the message:
-[storeSinglePlayer respondsToSelector:]: message sent to deallocated instance 0xd642df0
This is a very famous problem indeed. And as you'll notice in the code above, I have incorporated the suggestions I came across various posts on stackoverflow. The following code has been implemented:
[self.request cancel];
self.request.delegate = nil;
self.request = nil;
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
Also I have my property and corresponding synthesize set as:
#property (nonatomic, strong) SKProductsRequest *request;
#synthesize request = _request;
I have no clue, why is it crashing!
P.S: The project is ARC enabled.
Something is calling your ssp after it has been released by ARC. The code you posted doesn't make it clear where that is happening.
One thing that most likely would solve the problem would be to keep a reference with a member variable+property to your ssp in your class declaration, instead of createing a new one every time you click the button. Just init it once when your first viewcontroller loads and reuse it instead.
If you want to investigate further, you could try to comment out the blocks setting up the transition and see if it still crashes. Maybe the navigationcontroller makes some call after the ssp has been released.
When a message is sent to a deallocated instance, an object was released from the memory and you are attempting to use it again. ARC must be releasing the object since it is automatically detecting that you no longer need the it. Try adding one of the following to your header file.
#property (nonatomic, strong) storeSinglePlayer *ssp;
or
storeSinglePlayer *__strong ssp;
The important word above is strong. It tells ARC that you want to retain this object for later use.
I got where I was messing up. I was handling multiple rotations using the following code:
[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:#"storeSinglePlayerLandscape"] owner:self options:nil];
[self viewDidLoad];
I realize now, this not the best practice, as it calls viewDidLoad multiple times. Still, that's not really the cause of the problem. It works fine, when the store is displayed only a few times and returned back to the previous view controller. But say after 10-15 times, there are too many requests which would return with the product and hence send the error , "message sent to deallocated instance".
I commented out the code, and it works fine now.
I know, this is a very typical problem, which most users might not face. But just in case, you used implemented some bad code like me, may be this information works!

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

Cocos2d-iPhone: "update" called after dealloc

So I have a subclass of a CCSprite object, and in its init method, I call:
[self scheduleUpdate]
I later release this object from its parent CCNode like so:
[self removeChild:sprite cleanup:YES];
In addition, I call [self unscheduleUpdate] in the sprite's dealloc method.
However, I'm getting a bad memory access, so it appears that the update method is still attempted after the object is released (I've narrowed it down to this, as it works perfectly if I comment out the [self scheduleUpdate] line.
Any ideas?
Found this post in an attempt to ask the same question. I tried unschedule update (within my init method as well) with no luck, but then realized that by moving the [self unscheduleUpdate]; to the actual update method (which is running continuously, unlike the init method) based on a condition it worked!
So, for those looking for copy paste, here's a progress bar example that I'm implementing from http://www.ccsprite.com/cocos2d/using-ccprogresstimer-cocos2d-example.html#HCB_comment_box
-(id) init
{
//initialize progress bar, make sure to add a file named green_health_bar.png to your
//resource folder
timer = [CCProgressTimer progressWithFile:#"green_health_bar.png"];
//set the progress bar type to horizontal from left to right
timer.type = kCCProgressTimerTypeHorizontalBarRL;
//initialize the progress bar to zero
timer.percentage = 0;
//add the CCProgressTimer to our layer and set its position
[self addChild:timer z:1 tag:20];
[timer setPosition:ccp(100, 280)];
[self scheduleUpdate];
}
and in your update method:
-(void)update:(ccTime)dt
{
//get progress bar
CCNode* node = [self getChildByTag:20];
timer.percentage += dt * 10;
if (timer.percentage >= 100)
{
[self gameOver]; //used to stop parallax and show gameover menu
[self unscheduleUpdate];
}
}
I usually don't allow forum reply emails, but feel free to ask questions via #russ152!
Hmm.. try not to use scheduleUpdate? I tried looking for self unscheduleUpdate but there is not such function in a CCNode.. You can try [self unscheduleAllselectors], which stops all selectors of the object , including the update selector, if you are not using the object anymore.. Or use custom selectors instead..