An example: The following will ease-in the alpha from 0 to 1. When it reverses will it ease-in from 1 to 0, or will it reverse the timing function, and essentially use ease-out?
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"alpha"];
animation.fromValue = #0;
animation.toValue = #1;
animation.duration = 1.0;
animation.timingFunction = [CAMediaTimingFunction easeIn];
animation.autoreverses = YES;
It appears that the reversed animation does use a reversed timing function!
So in the above example:
0.0 –> 1.0 is eased in
1.0 –> 0.0 is eased out.
Related
I have a CAShapeLayer where I animate a circle. The animation is to first "undraw" the circle clockwise and then redraw the circle clockwise. Sort of a "rotating circle". Another way to put it: Move path stroke end point to start, then move the start point to the end.
The animation itself works, but it produces glitches now and then. It manifests in a short glimpse of the entire circle when it is supposed to be "undrawn".
Why does this occur and how can you fix it?
Thanks,
// Shape creation
layer.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, self.width - 2 * OUTER_BORDER_WIDTH, self.width - 2* OUTER_BORDER_WIDTH)].CGPath;
// Animation queuing
-(void) applyNextAnimation
{
CABasicAnimation* animation;
if (self.animatingOpening)
{
animation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat:1.0f];
self.animatingOpening = NO;
}
else
{
animation = [CABasicAnimation animationWithKeyPath:#"strokeStart"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat:1.0f];
self.animatingOpening = YES;
}
animation.duration = 1.0f;
animation.autoreverses = NO;
animation.delegate = self;
animation.removedOnCompletion = YES;
[self.outerCircleLayer addAnimation:animation forKey:#"stroke"];
}
// Animation stop callback
-(void) animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
if (self.isAnimating)
{
[self applyNextAnimation];
}
}
It blinks becuase you are not setting the corresponding property on the layer. So when the animation completes, the layer's model is still in the pre-animated state and that is what you see momentarily between the two animations.
This will get you towards what you want...
if (self.animatingOpening)
{
self.outerCircleLayer.strokeStart = 0.0;
animation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat:1.0f];
self.animatingOpening = NO;
}
else
{
self.outerCircleLayer.strokeStart = 1.0;
animation = [CABasicAnimation animationWithKeyPath:#"strokeStart"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat:1.0f];
self.animatingOpening = YES;
}
animation.duration = 1.0f;
animation.autoreverses = NO;
that almost works, but you will notice a more subtle glitch as you transition from the undrawn state to start animating the drawing state. The beginning of the circle has a small reverse animation as it starts. This is an implicit animation triggered by setting strokeStart from 1.0 to 0.0: which you need to get rid of so that all of the animations effects are under your control. You can achieve that most simply by setting disableActions to YES on CATransaction:
[CATransaction setDisableActions:YES];
( add it just above if (self.animatingOpening))
I've got a CABasicAnimation that is operating on the transform property of a CALayer.
The animation looks fairly cool and it all works fine, but now I want to know through which values the transform of the layer is going to go through for a perfect 30/60 FPS animation.
I don't want to run the aniamtion and measure the values, I want to know beforehand.
The timingFunction has to be taken into account for this calculation.
How can I find out these transform values?
That's the animation code:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath: #"transform"];
animation.fromValue = [NSValue valueWithCATransform3D: _myLayer.transform];
animation.toValue = valueTransform;
animation.duration = 0.7;
animation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut];
[_myLayer addAnimation: animation forKey:#"transformAnimation"];
i have modified this code to the code as shown below. I am using touch events to trigger the function below to run the animation. The results I am trying to achieve is: every time I touch 1 time, the image layer remove just processes 1 time. The problem I am facing is when I set removedOnCompletion to YES, whatever the layer just feels like it has removed 1 time but it is never removed, so the program is stuck on same layer. If I keep removedOnCompletion as NO, then image layer will auto remove all the layers.
So, how I can make sure that every time the process removes just 1 Image layer? Any help is appreciated.
-(void)animate:(id)sender {
//Dont implicitly animate the delay change
[CATransaction setDisableActions:YES];
//Reset the replicator delays to their origonal values
imageLayer.instanceDelay = X_TIME_DELAY;
//Re-enable the implicit animations
[CATransaction setDisableActions:NO];
//Create the transform matrix for the animation
//Move forward 1000 units along z-axis
CATransform3D t = CATransform3DMakeTranslation( -500, -500, 1000);
//Rotate Pi radians about the axis (0.7, 0.3, 0.0)
t = CATransform3DRotate(t, M_PI, 0.7, 0.3, 0.0);
//Scale the X and Y dimmensions by a factor of 3
t = CATransform3DScale(t, 0.5, 0.5, 1);
//Transform Animation
CABasicAnimation *animation = [CABasicAnimation animation];
animation.fromValue = [NSValue valueWithCATransform3D: CATransform3DIdentity];
animation.toValue = [NSValue valueWithCATransform3D: t];
animation.duration = 1.0;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeBoth;
[subLayer addAnimation:animation forKey:#"transform"];
//Opacity Animation
animation = [CABasicAnimation animation];
animation.fromValue = [NSNumber numberWithFloat:1.0];
animation.toValue = [NSNumber numberWithFloat:0.0];
animation.duration = 1.0;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeBoth;
[subLayer addAnimation:animation forKey:#"opacity"];
stuckfile = NO;
}
Consider the following animation:
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = 1.0;
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
pathAnimation.removedOnCompletion = NO;
pathAnimation.delegate = self;
This will essentially animate the drawing of the layer from one end to the next. The problem is that once the animation completes, the strokeEnd property resets back to 0 (where it was initially set). How do I make the final value "stick"?
I have attempted to change this in the animationDidStop delegate method. This mostly works, but can cause a flash of strokeEnd at 0 briefly, even when put inside a CATransaction to disable animations. I have also played with the additive and cumulative properties to no avail. Any suggestions?
You simply set the strokeEnd property to its final value yourself! Like this:
// your current code (stripped of unnecessary stuff)
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = 1.0;
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
// just add this to it:
theLayer.strokeEnd = 1;
// and now add the animation to theLayer
If strokeEnd were implicitly animatable (so that that line would itself cause animation) we would turn off implicit animation first:
[CATransaction setDisableActions:YES];
theLayer.strokeEnd = 1;
Note that your pathAnimation.removedOnCompletion = NO; is wrong, and so is the other answer's kCAFillModeForwards. Both are based on misunderstandings of how Core Animation works.
I have this code to animate a CALayer element.
CABasicAnimation *makeBiggerAnim=[CABasicAnimation animationWithKeyPath:#"radius"];
makeBiggerAnim.duration=0.2;
makeBiggerAnim.fromValue=[NSNumber numberWithDouble:20.0];
makeBiggerAnim.toValue=[NSNumber numberWithDouble:40.0];
makeBiggerAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
My question is, now everything works fine, I would like another attribute of the same element at the same time. I've seen you can do additive animations and stuff.
My question is:
Is the additive attribute the best / only way to do that? (animating at once multiple properties of the same object at once )
Thanks!
You can create an CAAnimationGroup and customize the duration and timing function on it. Then you create all your CABasicAnimations, set their to value and add them to the animation group. Finally, you add the animation group to the layer that you are animating.
Here an example:
CABasicAnimation *makeBiggerAnim=[CABasicAnimation animationWithKeyPath:#"cornerRadius"];
makeBiggerAnim.fromValue=[NSNumber numberWithDouble:20.0];
makeBiggerAnim.toValue=[NSNumber numberWithDouble:40.0];
CABasicAnimation *fadeAnim=[CABasicAnimation animationWithKeyPath:#"opacity"];
fadeAnim.fromValue=[NSNumber numberWithDouble:1.0];
fadeAnim.toValue=[NSNumber numberWithDouble:0.0];
CABasicAnimation *rotateAnim=[CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
rotateAnim.fromValue=[NSNumber numberWithDouble:0.0];
rotateAnim.toValue=[NSNumber numberWithDouble:M_PI_4];
// Customizing the group with duration etc, will apply to all the
// animations in the group
CAAnimationGroup *group = [CAAnimationGroup animation];
group.duration = 0.2;
group.repeatCount = 3;
group.autoreverses = YES;
group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
group.animations = #[makeBiggerAnim, fadeAnim, rotateAnim];
[myLayer addAnimation:group forKey:#"allMyAnimations"];
let groupAnimation = CAAnimationGroup()
groupAnimation.beginTime = CACurrentMediaTime() + 0.5
groupAnimation.duration = 0.5
let scaleDown = CABasicAnimation(keyPath: "transform.scale")
scaleDown.fromValue = 3.5
scaleDown.toValue = 1.0
let rotate = CABasicAnimation(keyPath: "transform.rotation")
rotate.fromValue = .pi/10.0
rotate.toValue = 0.0
let fade = CABasicAnimation(keyPath: "opacity")
fade.fromValue = 0.0
fade.toValue = 1.0
groupAnimation.animations = [scaleDown,rotate,fade]
loginButton.layer.add(groupAnimation, forKey: nil)
This is for the newest update on swift (swift 3). Your code should include a object at the end, i.e. UIButton, UILabel, something that you can animate. In my code it was the loginButton (which was the title or name).
In Swift-3 we can use CAAnimationGroup as below :-
let position = CAKeyframeAnimation(keyPath: "position")
position.values = [ NSValue.init(cgPoint: .zero) , NSValue.init(cgPoint: CGPoint(x: 0, y: -20)) , NSValue.init(cgPoint: .zero) ]
position.timingFunctions = [ CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) ]
position.isAdditive = true
position.duration = 1.2
let rotation = CAKeyframeAnimation(keyPath: "transform.rotation")
rotation.values = [ 0, 0.14, 0 ]
rotation.duration = 1.2
rotation.timingFunctions = [ CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut), CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) ]
let fadeAndScale = CAAnimationGroup()
fadeAndScale.animations = [ position, rotation]
fadeAndScale.duration = 1