Will This CAAnimation Cause a Leak or Retain Cycle? - iphone

Question: If the ViewController containing the UIView to which the animation below was applied to was deallocated, would it cause a memory leak or retain cycle?
In other words, if I applied this animation to a uiview, would it cause a memory leak or retain cycle when the uiview's parent VC is dismissed or deallocated?
+(CAAnimation*)fadeOfRoomStatusLabel
{
//Customize animation
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"opacity"];
animation.FromValue = [NSNumber numberWithFloat:0.2f];
animation.toValue = [NSNumber numberWithFloat:1.0f];
animation.autoreverses = YES;
//animation.BeginTime = CACurrentMediaTime()+.8;
//animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
animation.removedOnCompletion = NO;
animation.duration = 1;
animation.repeatCount = 99;
return animation;
}

No, it wouldn't since it doesn't have any explicitly set reference back to the view it's attached to. However, if you later set the animation's delegate to an object that has a strong reference to the animation (directly or indirectly), you will have a retain cycle, since the animation instance will retain its delegate. You will have to clear the delegate at some point in order for it to be released.
It's very easy to test these. Simply add a debug logging message to your view controller's -dealloc method. When you dismiss your view controller, ensure that you see the log message from its -dealloc method. If you don't, you know you have a memory issue with that view controller somewhere, and you can begin debugging why.

Related

Switching views using Core Animation not working?

Currently I am having a issue switching views using Core Animation. I want to fade through black switching to my next view.
Right now it does not do anything besides lose touch events from my original view.
What am I doing wrong in the code below?
Edit1 Code:
- (void)changeView1ToView2 {
CABasicAnimation *fadeout= [CABasicAnimation animationWithKeyPath:#"opacity"];
[fadeout setDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:)];
[fadeout setToValue:[NSNumber numberWithFloat:0]];
[fadeout setDuration:0.5];
[[self.view layer] addAnimation:fadeout forKey:#"alpha"];
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
[self.view addSubview:self.view2.view];
self.view2.view.frame = [UIScreen mainScreen].bounds;
[self.view2.view setAlpha:0];
CABasicAnimation *fadein = [CABasicAnimation animationWithKeyPath:#"opacity"];
[fadein setToValue:[NSNumber numberWithFloat:1.0]];
[fadein setDuration:0.5];
[[self.view2.view layer] addAnimation:fadein forKey:#"alpha"];
}
Ok I added self, look at my new code above. view2 is a UIViewController, thats why I am doing .view after it. The app is only going to be available on iOS 5 or up so thats not a problem. But what I am trying to achieve is switching views using Core Animation, and have each UIViewController manage their own views. I am just switching views using Core Animation instead of usual means.
If you're looking to have only one root view on screen at one time (and by the looks of that call to [UIScreen mainScreen].bounds, you are), then I'd suggest swapping the views at the UIWindow level. Without animations, this would look something like this:
// Assuming UIViewControllers called view1 and view2 as members of some
// (non-UIViewController) controller class (self, in this case)
//and that view1.view is in the application's window's subviews collection
[self.view1.view removeFromSuperview];
[[UIApplication sharedApplication].delegate.window addSubview:self.view2.view];
In terms of not seeing the views actually swap, you need to ensure that your animation preserves the changes you make during the animation. Specifically, you need to set the following:
myAnimation.fillMode = kCAFillModeForwards;
myAnimation.removedOnCompletion = NO;
Your methods can then be adjusted to take all this into account. For example, your animationDidStop:finished: method might look like this:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
[self.view1.view removeFromSuperview];
[self.view2.view setAlpha:0];
[[UIApplication sharedApplication].delegate.window addSubview:self.view2.view];
CABasicAnimation *fadein = [CABasicAnimation animationWithKeyPath:#"opacity"];
[fadein setToValue:[NSNumber numberWithFloat:1.0]];
[fadein setDuration:0.5];
fadein.fillMode = kCAFillModeForwards;
fadein.removedOnCompletion = NO;
[[self.view2.view layer] addAnimation:fadein forKey:#"alpha"];
}
You may need to muck around with it a bit to ensure that everything is firing correctly.

Animation stops when using UINavigationController or UITabView

I have a strange bug that I can't seem to figure out. I'm creating a little shining animation, which works perfectly, but for some reason stops when I navigate to another view via UINavigationController or UITabView (strangely modal view's don't affect it). Any ideas why, and how I can make sure the animation doesn't stop?
UIView *whiteView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[whiteView setBackgroundColor:[UIColor whiteColor]];
[whiteView setUserInteractionEnabled:NO];
[self.view addSubview:whiteView];
CALayer *maskLayer = [CALayer layer];
maskLayer.backgroundColor = [[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:0.0f] CGColor];
maskLayer.contents = (id)[[UIImage imageNamed:#"ShineMask.png"] CGImage];
// Center the mask image on twice the width of the text layer, so it starts to the left
// of the text layer and moves to its right when we translate it by width.
maskLayer.contentsGravity = kCAGravityCenter;
maskLayer.frame = CGRectMake(-whiteView.frame.size.width,
0.0f,
whiteView.frame.size.width * 2,
whiteView.frame.size.height);
// Animate the mask layer's horizontal position
CABasicAnimation *maskAnim = [CABasicAnimation animationWithKeyPath:#"position.x"];
maskAnim.byValue = [NSNumber numberWithFloat:self.view.frame.size.width * 9];
maskAnim.repeatCount = HUGE_VALF;
maskAnim.duration = 3.0f;
[maskLayer addAnimation:maskAnim forKey:#"shineAnim"];
whiteView.layer.mask = maskLayer;
Your maskAnim is retained by maskLayer, which is retained by whiteView's layer, which is retained by whiteView, which is retained by self.view. So this entire object graph will live until your view controller's view gets dealloc'd.
When you navigate away from your view controller, UIKit unloads your view to free up memory. When the view gets dealloc'd, so does your maskAnim. When you navigate back to your view controller, UIKit reconstructs your view hierarchy, either by reloading it from its .xib, or by calling loadView, depending on which technique you used.
So you need to make sure that the code you used to set up your maskAnim gets called again after UIKit reconstructs your view hierarchy. There are four methods you might consider: loadView, viewDidLoad, viewWillAppear:, and viewDidAppear:.
loadView is a sensible option if you use that method to build your view hierarchy (as opposed to loading it from a .xib), but you'll have to change your code so that it doesn't depend on the self.view property, which would trigger loadView recursively. viewDidLoad is also a good choice. With either of these options you you need to be careful because UIKit might resize your view after viewDidLoad if it wasn't constructed at the right size. This could cause bugs since your code depends on self.view.frame.size.width.
If you set up your animation in viewWillAppear: or viewDidAppear:, you can be sure that your view's frame will be the proper dimension, but you need to be careful with these methods because they can get called more than once after the view gets loaded, and you don't want to add your whiteView subview more than once.
What I'd probably do is make whiteView a retained property, and lazy load it in viewWillAppear: like this:
- (UIView *)setupWhiteViewAnimation {
// Execute your code above to setup the whiteView without adding it
return whiteView;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (!self.whiteView) {
self.whiteView = [self setupWhiteViewAnimation];
[self.view addSubview:self.whiteView];
}
}
- (void)viewDidUnload {
self.whiteView = nil; // Releases your whiteView when the view is unloaded
[super viewDidUnload];
}
- (void)dealloc {
self.whiteView = nil; // Releases your whiteView when the controller is dealloc'd
[super dealloc];
}

CABasicAnimation stops when relaunching the app

I am running into a problem where a restart of my iPhone app causes animations to stop. More specifically, I have the following animation set and running:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.duration = 1.0;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.repeatCount = 1e100f; // Infinite
animation.autoreverses = YES;
animation.fromValue = animationStartPath;
animation.toValue = animationFinishPath;
[view.layer addAnimation:animation forKey:#"animatePath"];
When I press the home key (iOS 4 so it is still 'running' in the background) and then relaunch the program, the animation has stopped. Is there any way to prevent this or easily restart them?
There are two methods in your app delegate where you can pass down information to your view controller that is performing the animation.
- (void)applicationWillResignActive:(UIApplication *)application
{
// Make note of whether or not the animation is running.
// Using NSUserDefaults is probably the simplest
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Check your user default setting to see if the animation
// was running when the app resigned active and then restart it
}
Of course this means you'll need a reference to your view controller that is performing the animation in your app delegate, or you could use notifications to pass the notification along. Anyhow, the bottom line is you'll have to watch for the app becoming active again and restart the animation.

Layer animation works fine first time, but not on second identical invocation

I have an animation that works perfectly on the first invocation. But if I want to animate the very same layer again, using the same code, it completes immediately and the animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag is invoked on the delegate with the flag value NO.
Here is the code that adds the animation:
imageView.hidden = NO;
CAKeyframeAnimation* animationOpacity =
[CAKeyframeAnimation animationWithKeyPath:#"opacity"];
...
animationOpacity.duration = 2.0;
animationOpacity.removedOnCompletion = YES;
animationOpacity.delegate = self;
[imageView.layer addAnimation:animationOpacity forKey:#"someKey"];
and this is the delegate action:
-(void) animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
imageView.hidden = YES;
}
BTW, Initially the imageView is visible in the XIB.
Turns out to be a combination of setting view.hidden = YES in the callback and calling the animation code from the parent's viewWillApear. Once I moved the animation code call into parent's viewDidApear instead, things started behaving as expected.
Are you adding the animation again before calling it a second time? If you aren't then you should set removedOnCompletion = NO
animationOpacity.removedOnCompletion = NO;

After Animation, View position resets

I am trying to make a view slide from top to bottom. This is not a big deal, I used CABasicAnimation for this. The problem is when I want to remove the view. I use this animation.
CABasicAnimation *animation;
animation = [CABasicAnimation animationWithKeyPath:#"position"];
[animation setDelegate:self];
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(self.view.layer.position.x, 0 - self.view.bounds.size.height / 2)];
animation.fromValue = [NSValue valueWithCGPoint:self.view.layer.position];
animation.autoreverses = NO;
animation.repeatCount = 0;
animation.duration = 0.25;
animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut];
[self.view.layer addAnimation:animation forKey:#"moveX"];
Which animates the view perfectly. But, after the animation finishes, my view appears again. So I added this line :
[self.view removeFromSuperview];
Which removes the view, but with no animation. So I decided to add the remove code to this delegate:
-(void) animationDidStop:(CAAnimation *) animation finished:(bool) flag
So now, the animation works, the view disappears, but sometimes, I can see the view appear and disappear faster, is like after the animation, the view appears, then the animationDidStop delegate is called, and the view disappears, obviously this is awful. What am I doing wrong?
Might want to set these properties. They cause the presentation to be preserved at the end of the animation.
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
Then the "animationDidStop:" method can be used to remove the view at the end of the animation:
-(void) animationDidStop:(CAAnimation *) animation finished:(bool) flag {
if (animation == [containerView.layer animationForKey:#"moveX"]) {
// remove view here, add another view and/or start another transition
}
}
Well, according to the Apple sample "MoveMe", this (removedOnCompletion) should work, however, it doesn't seem to.
So, add these lines after your code:
[self.view.layer addAnimation:animation forKey:#"moveX"];
self.view.layer.position = [animation.toValue CGPointValue];
This ensures that after the animation runs, the layer is properly positioned.
I had this issue when performing several animations in an animation group. I had to set a couple properties on the animation group itself, not the individual animations.
CAAnimationGroup *animGroup = [CAAnimationGroup animation];
// MAKE SURE YOU HAVE THESE TWO LINES.
animGroup.removedOnCompletion = NO;
animGroup.fillMode = kCAFillModeForwards;
animGroup.animations = [NSArray arrayWithObjects:moveAnim, scaleAnim, nil];
animGroup.duration = tAnimationDuration;
[tImageView.layer addAnimation:animGroup forKey:nil];
This one bit me too. You want to set the animation's removedOnCompletion flag to NO. It defaults to YES, which means after the animation is complete, it's removed, and the view reverts to its initial state.
Setting the view to hidden as Rob suggests should do it.
For properties of properties I would stick with the ObjC 2.0 style like you already have in your code.
set.view.hidden = YES;
Can you set the view's hidden property to YES?
I think it would be:
self.view.hidden = YES;
But it might be:
[self.view setHidden:YES];
I turns out I am pretty lame at figuring out the proper way to access properties of properties.