How to remove animation (CABasicAnimation)? - iphone

I want to remove an animation (CABasicAnimation) before it has completed.
For example:
In my code, I start a needle animating from a rotation value of 0 to a target value of 5. How do I stop the animation when the needle reaches a rotation value of 3?
CALayer* layer = someView.layer;
CABasicAnimation* animation;
animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.fromValue = [NSNumber numberWithFloat:0];
animation.toValue = [NSNumber numberWithFloat:5];
animation.duration = 1.0;
animation.cumulative = YES;
animation.repeatCount = 1;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[layer addAnimation:rotationAnimation forKey:#"transform.rotation.z"];

[layer removeAnimationForKey:#"transform.rotation.z"];
layer.removeAnimation(forKey: "bounce")
There's also
layer.removeAllAnimations()

You can monitor the current value of the transformation by looking at the presentationLayer attribute on the CALayer. The presentation layer holds the current values mid-animation.
IE:
CALayer *pLayer = [layer presentationLayer];
NSLog(#"Currently at %#", [pLayer valueForKeyPath:#"transform.rotation.z"]);
To stop the animation at 3, I would grab the layer, remove the 0-5 animation, start a new animation using the fromValue out of the presentationLayer and the toValue of 3. Setting the duration of the animation depends on the behavior of the animation, but if you want the animation to take 3/5's of a second to complete if it stops at 3, then you would find how far into the animation you are by looking at how far into the 0-5 animation you are, then subtracting that from 3/5's of a second to get the remainder of time you want to use.
I learned a ton on CoreAnimation from Marcus Zarra at the Voices That Matter iPhone conference. I'd recommend following his blog and buying his book!

Change:
animation.toValue = [NSNumber numberWithFloat:5];
to
animation.toValue = [NSNumber numberWithFloat:3];

Related

CABasicAnimation CorePlot scale in effect

I am trying to scale in a core plot scatter plot (like a line graph) so that it scales in to view. CorePlot uses a normal CALayer to implement its plot.
I am using Core animation as follows to achieve this:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"affineTransform"];
animation.duration = 10;
animation.removedOnCompletion = YES;
animation.fillMode = kCAFillModeForwards;
CGAffineTransform start = CGAffineTransformMakeScale(1, 0.1);
CGAffineTransform end = CGAffineTransformMakeScale(1, 1);
animation.fromValue = [NSValue valueWithCGAffineTransform:start];
animation.toValue = [NSValue valueWithCGAffineTransform:end];
[plot addAnimation:animation forKey:#"animation"];
The problem is this doesn't seem to work. This is the first time I am using core animation so I am thinking I am just doing something wrong?
EDIT
OK so I found the issue I was using the wrong keypath.
I can get it to animate now but it looks wierd:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.scale.y"];
...
animation.fromValue = [NSNumber numberWithFloat:0.1];
animation.toValue = [NSNumber numberWithFloat:1];
What I want to do is have the animation 'scale upwards' so it kind of stretches in to place. Right now it kind of grows out from the centre which is not what I want. Any idea how to achieve that?
The transform is relative to the anchorPoint of the layer. You can change the anchor point to be at the bottom by setting it to (0.5, 1,0). Note that the layer also draws relative to the anchor point so it may change its position on screen when you change it. To position it where it should be you could either set the frame after changing the anchor point or set the position to the bottom (where it should scale from).

Why CABasicAnimation will send the layer's view to back and front?

There are two UIViews of similar size. UIView one (A) is originally on top of UIView two (B). When I try to perform a CABasicAnimation transform.rotation.y on A's layer:
CABasicAnimation *rotateAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
CGFloat startValue = 0.0;
CGFloat endValue = M_PI;
rotateAnimation.fromValue = [NSNumber numberWithDouble:startValue];
rotateAnimation.toValue = [NSNumber numberWithDouble:endValue];
rotateAnimation.duration = 5.0;
[CATransaction begin];
[imageA.layer addAnimation:rotateAnimation forKey:#"rotate"];
[CATransaction commit];
During the animation, the animating layer's UIView (A) will be:
sent back (A is suddenly behind B)
rotating ... passed second half of the animation
sent front (A is now on top of B again)
Is there a way to keep A on top of B for the whole animation period? Thanks!
UPDATE: project source is attached: FlipLayer.zip
The problem is a rotation around the Y axis will, in a true 3D compositing system, make layer A intersect with layer B, such that half of layer A should be visible and half should be behind layer B. CoreAnimation does not provide a true 3D compositing system. Instead it attempts to determine which layer is in front of which other layer, and renders it entirely without intersection. In your case, during the first half of the rotation CoreAnimation has decided that layer B is on top of layer A in the 3D space. The simplest solution is probably to set the zPosition of layer A to layer.bounds.size.width (default is 0) for the duration of the animation (or permanently, if it should always be on top).
If you want to set it only for the duration of the animation, you can add a second animation for the transform.translation.z key path. Here's your -flip method modified to use this:
- (IBAction)flip:(id)sender {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
CGFloat startValue = 0.0;
CGFloat endValue = M_PI;
animation.fromValue = [NSNumber numberWithDouble:startValue];
animation.toValue = [NSNumber numberWithDouble:endValue];
animation.duration = 5.0;
[imageOne.layer addAnimation:animation forKey:#"rotate"];
animation.keyPath = #"transform.translation.z";
animation.fromValue = [NSNumber numberWithFloat:imageOne.layer.bounds.size.width];
animation.toValue = animation.fromValue;
[imageOne.layer addAnimation:animation forKey:#"zPosition"];
}
Have you tried adding a [self.view bringSubviewToFront:imageA]; in your animation sequence?
You could try to translate on the z axis and bring A forward first. Perform the rotation and then set the z back to its starting value.
Also, you probably won't see any perspective on the rotation unless you set the .m34 property on the transform. Try something like this:
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0f / -300.0f;
transform = CATransform3DRotate(transform, 0.0f, M_PI, 0.0f);
CABasicAnimation *rotateAnimation = [CABasicAnimation
animationWithKeyPath:#"transform"];
rotateAnimation.fromValue = [NSValue
valueWithCATransform3D(CATransformIdentity)];
rotateAnimation.toValue = [NSValue valueWithCATransform3D(transform)];
rotateAnimation.duration = 5.0;
[imageA.layer addAnimation:rotateAnimation forKey:#"rotate"];
You transaction block is unnecessary.
Best Regards.

How to replicate the Scale Up Animation when an app starts regularly (push app icon on HomeScreen) on an iPhone

i want to replicate the animation when an app starts on iPhone.
There is the first view scaling up from 50 % to 100% i think.
later I want to use this as a transition between 2 views.
Any ideas how to replicate, or is there a ready to use solution from apple in the sdk?
Thank you very much :)
You can do the same thing with CABasicAnimation and CAAnimationGroup, I actually thought that Core Animation over UIKit Animations was smoother and It gives you more control.
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.removedOnCompletion = YES;
CABasicAnimation *fadeAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
fadeAnimation.fromValue = [NSNumber numberWithFloat:0.0];
fadeAnimation.toValue = [NSNumber numberWithFloat:1.0];
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
scaleAnimation.fromValue = [NSNumber numberWithFloat:0.5];
scaleAnimation.toValue = [NSNumber numberWithFloat:1.00];
animationGroup.animations = [NSArray arrayWithObjects:fadeAnimation, scaleAnimation, nil];
[self.layer addAnimation:animationGroup forKey:#"fadeAnimation"];
self.layer.opacity = 1.0;
"There's always more then one way to skin a cat"
You can apply a scale transform to the UIView and then animate it back to it's normal state.
// Set the the view's scale to half
[viewToScale setTransform:CGAffineTransformMakeScale(0.5,0.5)];
// Set the transform back to normal (identity) in an animation when you're
// ready to animate
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1.0];
[viewToScale setTransform:CGAffineTransformIdentity];
[UIView commitAnimations];
The trick will be you will need to set the view to the smaller scale in a separate run cycle from the animation or you won't see the view animate. So you can set the smaller scale and then call -performSelector:withObject:afterDelay: to actually perform the animation.

CABasicAnimation not changing its position property after animation completes

i am applying CABasicAnimation for position property of a layer it is animating well but after animation complete its coming back to original position how to avoid that and to make image to stay in animated position only?
There are a few different ways to solve this. My favorite is to set the object at the end of the animation, and use fromValue instead of toValue to create the animation. You can also set both fromValue and toValue to create this effect. ie
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.duration = 1.0;
animation.fromValue = [NSValue valueWithCGPoint:startPoint];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
viewToAnimate.center = endPoint;
[viewToAnimate.layer addAnimation:animation forKey:#"slide"];
The other way to do this is to set yourself as the delegate and implement:
-(void)animationDidStop:(id)sender finished:(BOOL)finished {
viewToAnimate.center = endPoint;
}
You need to set the final position. If you only use the animation fromValue and toValue it will snap back to the original starting point. The animation uses a copy of the view/layer as it "plays" and then the animation will show the original view/layer when it finishes.
If you don't explicitly set it, it will appear to snap back, even though the view/layer never really moved.
startPoint and endPoint are CGPoints and myView is the UIView that is being animated.
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.duration = .3;
animation.fromValue = [NSValue valueWithCGPoint:startPoint];
animation.toValue = [NSValue valueWithCGPoint:endPoint];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[myView.layer addAnimation:animation forKey:#"position"];
myView.layer.position = endPoint; // Must set end position, otherwise it jumps back
To set the object at the end of the animation just write the code like this
#import <QuartzCore/QuartzCore.h>
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.duration = 1.0;
animation.fromValue = [NSValue valueWithCGPoint:startPoint];
animation.timingFunction = [CAMediaTimingFunction functionWithName:easing];
animation.fillMode = kCAFillModeForwards; // This line will stop the animation at the end position of animation
animation.autoreverses = NO;
viewToAnimate.center = endPoint;
[viewToAnimate.layer addAnimation:animation forKey:#"slide"];
You can just set removedOnCompletion to NO if you want to preserve the final value. And don't forget to set the fillMode.
Swift 4+
animation.fillMode = .forwards;
animation.isRemovedOnCompletion = false
Objective-C
animation.fillMode = kCAFillModeForwards; //The receiver remains visible in its final state when the animation is completed.
animation.removedOnCompletion = NO;
If you look at the docs for addAnimation:forKey:, it says 'Typically this is implicitly invoked'. In other words, you're not supposed to call it directly. You're actually meant to do the following instead, which is even easier than setting from and to values, you simply set the values directly on the layer:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"opacity"];
animation.duration = .3;
myLayer.actions = #{#"opacity": animation};
myLayer.opacity = 0;
This will fade a layer to zero opacity.

How to get layer point?

I am using this code to rotate a needle (like in a speedometer):
CALayer* layer = someView.layer;
CABasicAnimation* animation;
animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.fromValue = [NSNumber numberWithFloat:0.0 * M_PI];
animation.toValue = [NSNumber numberWithFloat:1.0 * M_PI];
animation.duration = 1.0;
animation.cumulative = YES;
animation.repeatCount = 1;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[layer addAnimation:rotationAnimation forKey:#"transform.rotation.z"];
Now my question is that during the animation i press one button at that time needle is back to move on particular point. So i must have to get last point of layer when i touch button.
How to get last point of layer?
Your question is very hard to understand, but it seems like you are asking how to determine the state of a progressing animation at the moment a user presses a button. To do this, you can get a CALayer's presentationLayer, and query that for the current value of the property being animated.
The answers to this question show you how to determine the angle of your rotated presentationLayer. You probably want to ignore my needlessly complex answer there.