How to identify CAAnimation after completion? I tried KVO by setting a string value as animationID and checking it in animationDidStop method. But KVO returns an integer sometimes instead of a string animationID and the app is crashing.
Here is my code:
-(void)moveLayer:(CALayer*)layer to:(CGPoint)point duration:(NSTimeInterval)duration animationID:(NSString*)animationid
{
// Prepare the animation from the current position to the new position
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.fromValue = [layer valueForKey:#"position"];
[animation setDuration:duration];
animation.toValue = [NSValue valueWithCGPoint:point];
animation.delegate=self;
[animation setValue:animationid forKey:#"animationID"];
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
// Update the layer's position so that the layer doesn't snap back when the animation completes.
layer.position = point;
// Add the animation, overriding the implicit animation.
[layer addAnimation:animation forKey:#"position"];
}
and callback is:
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag
{
if (flag) {
id animationIDString=[animation valueForKey:#"animationID"];
// this is coming as an int value sometimes and the code below this fails ..
if([animationIDString isEqual:#"animation1"]) {
//animation is animation1
_movieBalloonImageView.hidden=NO;
_storiesBalloonImageView.hidden=NO;
_rhymesBalloonImageView.hidden=NO;
[self.navigationController popViewControllerAnimated:NO];
}
}
}
What can be the possible reason? Is it because the animation gets destroyed before this delegate gets called?
I am setting removedOnCompletion to NO and fillmode to kCAFillModeForwards.But the code is not working as expected.
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
Or is there any alternative method to do this? I am calling the moveLayer method in several places in the viewController.
Related
I am having an issue with iOS CABasicAnimation. No matter what I do, I cannot get the methods animationDidStart: and animationDidStop:finished: to fire. My class is subclassing CAShapeLayer and is performing the animations inside of it:
- (void)start{
[self removeAllAnimations];
CABasicAnimation *pathAnimation = [self makeAnimationForKey:#"strokeEnd"];
[self addAnimation:pathAnimation forKey:#"strokeEnd"];
}
- (CABasicAnimation *)makeAnimationForKey:(NSString *)key {
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:key];
anim.fromValue = [NSNumber numberWithFloat:0.f];
anim.toValue = [NSNumber numberWithFloat:1.f];
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
anim.duration = self.duration;
anim.delegate = self;
return anim;
}
- (void)animationDidStart:(CAAnimation *)anim{
NSLog(#"HERE START");
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
NSLog(#"HERE STOP");
}
Any tips or help would be appreciated, thanks in advance!
you must set delegate before assigning animation to layer:
popIn.delegate = self;
[annotation.layer addAnimation:popIn forKey:#"popIn"];
Swift 5.4.2
popIn.delegate = self
annotation.layer.add(transition, forKey:"popIn")
Ok so it turns in my subclass I had a property called duration. Even though it is not documented as being apart of CALayer, duration is a part of one of it's protocols called CAMediaTiming. The methods were never fired because the property was being overwritten via my subclass.
Good Evening!
I have a peace of code but it is not calling the animationdidstop method. I could not identify why it does not work. I`ve tried many solutions..
-(IBAction)MakeCircle:(id)sender{
// Add to parent layer
[self.view.layer addSublayer:circle];
// Configure animation
drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 5.0;
drawAnimation.repeatCount = 1.0;
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:1.0f];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
// Add the animation
[circle addAnimation:drawAnimation forKey:#"drawCircleAnimation"];}
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
if(anim == [self.view.layer animationForKey:#"drawCircleAnimation"]){
Label.text = [NSString stringWithFormat:#"Loser"];
}
}
Thanks!!
You are not setting yourself as the delegate of the animation. Add this line before adding the animation:
drawAnimation.delegate = self;
Edit:
Oh. I know exactly what the problem is. The system copies your animation object when you submit the animation, so it won't be the same object in the completion routine. Try adding a unique key to the animation and checking that instead of checking to see if it's the same animation object you submitted.
e.g.:
drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
[drawAnimation setValue: #"mydrawCircleAnimation" forKey: #"animationKey"];
//The rest of your animation code...
Then in your animationDidStop:
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
if([anim valueForKey: #"animationKey"
isEqualToString: #"mydrawCircleAnimation"])
{
Label.text = [NSString stringWithFormat:#"Loser"];
}
}
Edit #2:
There is an even cleaner way to handle animation completion code. You can attach a block to your animations and then write a general animationDidStop:completion: method that looks for the block and invokes it if found. See my answer in this thread:
How to identify CAAnimation within the animationDidStop delegate?
I want to use the CABasicAnimation to flip the UILabel. Animation will repeat forever and will change the text of UILabel between two different values.
- (void)animateLabel
{
[self.myLabel.layer addAdnimation:[self labelAnimation] forKey:#"flip"];
}
- (CAAnimation*)labelAnimation
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
[animation setRepeatCount:NSIntegerMax];
[animation setAutoreverses:YES];
[animation setDuration:2.0];
[animation setDelegate:self];
CATransform3D transform = CATransform3DMakeRotation(M_PI_2, 1, 0, 0);
[animation setToValue:[NSValue valueWithCATransform3D:transform]];
return animation;
}
Now, I tried using the delegate but the delegate method is only when the animation first begins. Rather I need to be able to know the the label completes one cycle. Is there some convenience method or way to do it with CALayer or do I have to use CADisplay link or timer ? I would like to thank you before hand for helping me.
To get notified on each cycle, setRepeatsCount to 1. You'll then get notified in the delegate animationDidStop:finished. You would then simply add the same animation again.
- (void)animateLabel
{
[self.myLabel.layer addAdnimation:[self labelAnimation] forKey:#"flip"];
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
[self animateLabel];
// do other stuff
}
- (CAAnimation*)labelAnimation
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
[animation setRepeatCount:1];
[animation setAutoreverses:YES];
[animation setDuration:2.0];
[animation setDelegate:self];
CATransform3D transform = CATransform3DMakeRotation(M_PI_2, 1, 0, 0);
[animation setToValue:[NSValue valueWithCATransform3D:transform]];
return animation;
}
No, this is the way you're supposed to do it according to the documentation.
Setting this property to HUGE_VALF will cause the animation to repeat forever.
I'm animating a CALayer's opacity-property with the following code:
Creating the animation in a method:
+ (CABasicAnimation *)fadeIn:(float)begin duration:(float)duration remove:(BOOL)remove{
CABasicAnimation *fadeAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
fadeAnimation.fromValue = [NSNumber numberWithFloat:0.0];
fadeAnimation.toValue = [NSNumber numberWithFloat:1.0];
fadeAnimation.additive = NO;
fadeAnimation.removedOnCompletion = remove;
fadeAnimation.beginTime = begin;
fadeAnimation.duration = duration;
fadeAnimation.fillMode = kCAFillModeBoth;
return fadeAnimation;
}
Adding the animation to the layer:
[overlayLayer addAnimation:[VideoComposerHelpers fadeIn:1.0 duration:0.5 remove:NO] forKey:nil];
This is working perfect. However, now I want to add another animation to the same layer, right after the first animation finished.
[overlayLayer addAnimation:[VideoComposerHelpers fadeOut:1.5 duration:0.5 remove:NO] forKey:nil]; // fadeOut is a method similar to fadeIn
What should happen is, the layer fades in with a duration of 0.5 and right after that, it fades out with a duration of 0.5.
This doesn't seem to work, though. Is it because the start point of the second animation is the same as the end point of the first one?
You should use a CAAnimationGroup something like this:
CABasicAnimation *fadeIn = [VideoComposerHelpers fadeIn:1.0 duration:0.5 remove:NO];
CABasicAnimation *fadeOut = [VideoComposerHelpers fadeOut:1.5 duration:0.5 remove:NO];
CAAnimationGroup *group = [CAAnimationGroup animation];
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;
[group setAnimations:[NSArray arrayWithObjects:fadeIn, fadeOut, nil]];
group.duration = 2;
[overlayLayer addAnimation:group forKey:#"savingAnimation"];
Also I'm not sure if I get the right values vor start, end, duration of the animations (you should check them) :)).
A CABasicAnimation can have a delegate. The delegate will be sent animationDidStop:finished:. Now you can ask for the next animation in the chain.
In your case, though, you don't even need that; just use an animation where autoreverses is YES.
(Oh, and do not mess with removedOnCompletion and fillMode; that's just wrong, and examples that use them are equally wrong. They are needed only in the middle complex grouped animations.)
When I launch my application and go to flipSideViewController from mainViewController the image view in flipSideViewController will animate. I want to animate it every time the user
transitions from mainViewController to flipSideViewController.
Here's some code:
[self animate];
//Not sure where to put this instance method. Would I put this in a certain method? I have no idea what I would write for it!
- (void)animate
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
animation.toValue = [NSNumber numberWithFloat:((-10*M_2_PI)/180)];
animation.duration = .3;
animation.autoreverses = YES;
animation.repeatCount = 4;
animation.speed = 9;
pwAnimation.removedOnCompletion = YES;
[_ImageView.layer addAnimation:pwAnimation forKey:#"rotation"];
}
Use ViewDidAppear delegate method of flipSideViewController to invoke your -animate method