Why are my animation values ignored? - iphone

I try to do a CAKeyFrameAnimation for rotating an layer:
CALayer* theLayer = myView.layer;
CAKeyframeAnimation* animation;
animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.duration = 1.0;
animation.cumulative = NO;
animation.repeatCount = 1;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
animation.values = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0 * M_PI],
[NSNumber numberWithFloat:0.5 * M_PI],
[NSNumber numberWithFloat:0.3 * M_PI], // animation stops here...
[NSNumber numberWithFloat:0.8 * M_PI], // ignored!
[NSNumber numberWithFloat:0.7 * M_PI], nil]; // ignored!
animation.keyTimes = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.2],
[NSNumber numberWithFloat:2.0], // ignored!
[NSNumber numberWithFloat:1.5], // ignored!
[NSNumber numberWithFloat:2], nil]; // ignored!
animation.timingFunctions = [NSArray arrayWithObjects:
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear], nil];
[theLayer addAnimation:animation forKey:#"transform.rotation.z"];
Like you can see in the comments, the animation only runs two of the key frames, but not all of them. No matter what kind of values I put in there, the animation will never run more than two key frames.
What could be wrong there?

Could it be that you set the animation.duration to 1 and yet your keyTimes are 0, 0.2 and then 2... meaning that the animation will stop before it reaches your 3rd value.

The keyTimes array must only contain increasing values from 0.0 to 1.0. These are percentages of the progress through the animation, not raw numbers in seconds. So, the keyframe matching keyTime 0.5 will happen halfway through the animation, not 1/2 second into the animation (unless, of course, you have a one-second animation).

Related

Jerky Animation for UiView PopOut

I have a UIView for which I am using the following code for pop-in animation
float duration=1.3f;
CAKeyframeAnimation *scale = [CAKeyframeAnimation animationWithKeyPath:#"transform.scale"];
scale.duration = duration;
scale.values = [NSArray arrayWithObjects:[NSNumber numberWithFloat:.5f],
[NSNumber numberWithFloat:1.1f],
[NSNumber numberWithFloat:0.95f],
[NSNumber numberWithFloat:1.f],
nil];
CABasicAnimation *fadeIn = [CABasicAnimation animationWithKeyPath:#"opacity"];
fadeIn.duration = duration * .2f;
fadeIn.fromValue = [NSNumber numberWithFloat:0.f];
fadeIn.toValue = [NSNumber numberWithFloat:1.f];
fadeIn.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
fadeIn.fillMode = kCAFillModeForwards;
CAAnimationGroup *animationgroup = [CAAnimationGroup animation];
animationgroup.delegate=self;
animationgroup.animations = [NSArray arrayWithObjects:scale, fadeIn, nil];
animationgroup.duration = duration;
animationgroup.fillMode = kCAFillModeForwards;
animationgroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[animationgroup setValue:#"popIn" forKey:#"name"];
animationgroup.fillMode=kCAFillModeForwards;
[self.view.layer addAnimation:animationgroup forKey:#"popIn"];
That works fone. It pops In the view in perfect way. But the code of pop-out animation is gives a jerk at end of animation that looks quite wierd.
Here is my popout code
float duration=0.15f;
CAKeyframeAnimation *scale = [CAKeyframeAnimation animationWithKeyPath:#"transform.scale"];
scale.duration = duration;
scale.removedOnCompletion = NO;
scale.values = [NSArray arrayWithObjects:[NSNumber numberWithFloat:1.f],
[NSNumber numberWithFloat:1.1f],
[NSNumber numberWithFloat:.15f],
nil];
CABasicAnimation *fadeOut = [CABasicAnimation animationWithKeyPath:#"opacity"];
fadeOut.duration = duration * .4f;
fadeOut.fromValue = [NSNumber numberWithFloat:1.f];
fadeOut.toValue = [NSNumber numberWithFloat:0.f];
fadeOut.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
fadeOut.beginTime = duration * .6f;
fadeOut.fillMode = kCAFillModeBoth;
CAAnimationGroup *animationgroup = [CAAnimationGroup animation];
animationgroup.animations = [NSArray arrayWithObjects:scale, fadeOut, nil];
animationgroup.duration = duration;
animationgroup.fillMode = kCAFillModeForwards;
animationgroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[animationgroup setValue:#"popOut" forKey:#"name"];
animationgroup.fillMode=kCAFillModeForwards;
[self.view.layer addAnimation:animationgroup forKey:#"popOut"];
The scale sequence 1.0, 1.1, 0.15 should result in a jerk.
If you are not satisfied with the visual results, try fiddling with the values above and the timing function (you are using "easeIn" instead of "easeInOut").
During Animation only the presentation layer gets changes while the modal layer of the view stays to its original state.
In your case you are not preserving the state of presentation layer when animation is finished and Modal layer appears which is in it original form. which seems like the Jerk effect.
Use the following line after "animationgroup.fillMode=kCAFillModeForwards;" to preserve the presentation layer state and the jerk will go away.
animationgroup.removedOnCompletion=NO;

How do I use keytimes in CAKeyFrameAnimation to animate the opacity of a layer?

I am trying to animate a layer so that the opacity goes from 0 to 1 in about 0.2 seconds, keep the opacity at 1 for a second, and then put the opacity back at 0. I'm trying to use key times to do it, but I can't get it right.
CAKeyframeAnimation *opacityLabel = [CAKeyframeAnimation animationWithKeyPath: #"opacity"];
// 0.2 seconds fade in, 1 second hold, 0.2 seconds fade out
[opacityLabel setDuration: 1.4];
[opacityLabel setDelegate: self];
[opacityLabel setValue: #"countLabel" forKey: #"verify"];
[opacityLabel setValues: [NSArray arrayWithObjects: [NSNumber numberWithFloat: 1.0], [NSNumber numberWithFloat: 1.0], [NSNumber numberWithFloat: 0], nil]];
[opacityLabel setKeyTimes: [NSArray arrayWithObjects: [NSNumber numberWithFloat: 0.2], [NSNumber numberWithFloat: 1.2], [NSNumber numberWithFloat: 1.4], nil]];
[[tomorrowCountLabel layer] addAnimation: opacityLabel forKey: #"opacityUp"];
You are almost there. Key times should be values between 0 and 1. You can think of them as percentages. Also your animation should start at opacity 0 not 1. So here are the two lines you should change:
opacityLabel.values = #[#0, #1, #1, #0];
opacityLabel.keyTimes = #[#0, #(0.2/1.4), #(1.2/1.4), #1];

How Do You CAKeyframeAnimation Scale?

I want to create an animation with several key frames. I want my Layer (a button in this case)
to scale up to 1.5 then down to 0.5 then up to 1.2 then down to 0.8 then 1.0.
I also want to EaseIn and EaseOut of each keyframe.
As you can imagine, this will create a Springy/Bounce effect on the spot.
In other parts of my app I have been using CAKeyframeAnimation like this (see below code).
This creates a similar springy animation but for x and y position.
Can I adapt the below code to affect scale instead of position?
Thank you in advance!
- (CAAnimation*)monInAnimation {
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path,NULL,113,320);
CGPathAddLineToPoint(path, NULL, 113.5, 283);
CGPathAddLineToPoint(path, NULL, 113.5, 179);
CGPathAddLineToPoint(path, NULL, 113.5, 207);
CGPathAddLineToPoint(path, NULL, 113.5, 187);
CGPathAddLineToPoint(path, NULL, 113.5, 199);
CGPathAddLineToPoint(path, NULL, 113.5, 193);
CGPathAddLineToPoint(path, NULL, 113.5, 195);
CGPathAddLineToPoint(path, NULL, 113.5, 194);
CAKeyframeAnimation *
animation = [CAKeyframeAnimation
animationWithKeyPath:#"position"];
[animation setPath:path];
[animation setDuration:1.5];
[animation setCalculationMode:kCAAnimationLinear];
NSArray *arr = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.12],
[NSNumber numberWithFloat:0.24],
[NSNumber numberWithFloat:0.36],
[NSNumber numberWithFloat:0.48],
[NSNumber numberWithFloat:0.60],
[NSNumber numberWithFloat:0.72],
[NSNumber numberWithFloat:0.84],
[NSNumber numberWithFloat:1.0],nil];
[animation setKeyTimes:arr];
[animation setTimingFunctions:[NSArray arrayWithObjects:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut], nil]];
//[animation setAutoreverses:YES];
CFRelease(path);
return animation;
}
- (void)monBtnIn {
[monButton.layer setPosition:CGPointMake(113.5,194)];
[monButton.layer addAnimation:[self monInAnimation]
forKey:#"position"];
}
Two alternative solutions for you:
First, You can also animate the transform property.
Using Brad's code, but using #"transform" for the keypath. The primary advantage being that you do not have to calculate the actual frame, but instead provide a simple scaling factor:
Objective-C:
CAKeyframeAnimation *boundsOvershootAnimation = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
CATransform3D startingScale = CATransform3DScale (layer.transform, 0, 0, 0);
CATransform3D overshootScale = CATransform3DScale (layer.transform, 1.2, 1.2, 1.0);
CATransform3D undershootScale = CATransform3DScale (layer.transform, 0.9, 0.9, 1.0);
CATransform3D endingScale = layer.transform;
NSArray *boundsValues = [NSArray arrayWithObjects:[NSValue valueWithCATransform3D:startingScale],
[NSValue valueWithCATransform3D:overshootScale],
[NSValue valueWithCATransform3D:undershootScale],
[NSValue valueWithCATransform3D:endingScale], nil];
[boundsOvershootAnimation setValues:boundsValues];
NSArray *times = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:0.5f],
[NSNumber numberWithFloat:0.9f],
[NSNumber numberWithFloat:1.0f], nil];
[boundsOvershootAnimation setKeyTimes:times];
NSArray *timingFunctions = [NSArray arrayWithObjects:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
nil];
[boundsOvershootAnimation setTimingFunctions:timingFunctions];
boundsOvershootAnimation.fillMode = kCAFillModeForwards;
boundsOvershootAnimation.removedOnCompletion = NO;
Swift 4:
let boundsOvershootAnimation = CAKeyframeAnimation(keyPath: "transform")
let startingScale = CATransform3DScale(layer.transform, 0, 0, 0)
let overshootScale = CATransform3DScale(layer.transform, 1.2, 1.2, 1.0)
let undershootScale = CATransform3DScale(layer.transform, 0.9, 0.9, 1.0)
let endingScale = layer.transform
boundsOvershootAnimation.values = [startingScale, overshootScale, undershootScale, endingScale]
boundsOvershootAnimation.keyTimes = [0.0, 0.5, 0.9, 1.0].map { NSNumber(value: $0) }
boundsOvershootAnimation.timingFunctions = [
CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseOut),
CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut),
CAMediaTimingFunction(name:kCAMediaTimingFunctionEaseInEaseOut)
]
boundsOvershootAnimation.fillMode = kCAFillModeForwards
boundsOvershootAnimation.isRemovedOnCompletion = false
Second, and likely easier, is using FTUtils, an open source wrapper for core animation. It includes a stock "springy" animation.
You can get it at:
http://github.com/neror/ftutils
transform.scale, anyone?
let anim = CAKeyframeAnimation(keyPath: "transform.scale")
anim.values = [0, 1, 0, 1, 0, 1, 0]
anim.duration = 4.0
smallView.layer.addAnimation(anim, forKey: "what")
Totally unrelated, if you are gonna use floats in the values array, you must add them as NSNumbers, otherwise they'd just end up as 0s!
anim.values = [0.0, 1.2, 0.9, 1.0].map { NSNumber(double: $0) }
Rather than setting the path of your CAKeyframeAnimation, you'll want to set the keyframes themselves. I've created a "pop-in" effect before by animating the size of the bounds of a layer:
CAKeyframeAnimation *boundsOvershootAnimation = [CAKeyframeAnimation animationWithKeyPath:#"bounds.size"];
CGSize startingSize = CGSizeZero;
CGSize overshootSize = CGSizeMake(targetSize.width * (1.0f + POPINOVERSHOOTPERCENTAGE), targetSize.height * (1.0f + POPINOVERSHOOTPERCENTAGE));
CGSize undershootSize = CGSizeMake(targetSize.width * (1.0f - POPINOVERSHOOTPERCENTAGE), targetSize.height * (1.0f - POPINOVERSHOOTPERCENTAGE));
NSArray *boundsValues = [NSArray arrayWithObjects:[NSValue valueWithCGSize:startingSize],
[NSValue valueWithCGSize:overshootSize],
[NSValue valueWithCGSize:undershootSize],
[NSValue valueWithCGSize:targetSize], nil];
[boundsOvershootAnimation setValues:boundsValues];
NSArray *times = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:0.5f],
[NSNumber numberWithFloat:0.9f],
[NSNumber numberWithFloat:1.0f], nil];
[boundsOvershootAnimation setKeyTimes:times];
NSArray *timingFunctions = [NSArray arrayWithObjects:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
nil];
[boundsOvershootAnimation setTimingFunctions:timingFunctions];
boundsOvershootAnimation.fillMode = kCAFillModeForwards;
boundsOvershootAnimation.removedOnCompletion = NO;
where POPINOVERSHOOTPERCENTAGE is the fraction by which I wanted to overshoot the target size of the layer.
I don't know if you can use a CAKeyframeAnimation for animating the scale of a UIView, but you can do it with a CABasicAnimation and setting the fromValue and toValue properties, and using that to animate the transform property:
- (CAAnimation*)monInAnimation
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 1.0)];
animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(3.0, 3.0, 3.0)];
[animation setDuration:1.5];
[animation setAutoreverses:YES];
return animation;
}
- (IBAction)monBtnIn
{
[monButton.layer addAnimation:[self monInAnimation] forKey:#"transform"];
}

Basic keyframe animation (rotation)

I'm trying to create a very simple keyframe animation, whereby a graphic Rotates from one angle to another, through a given midpoint.
(The purpose is to be able to animate rotation through an OBTUSE angle of arc GREATER THAN 180 DEGREES, rather than having the animation 'cheat' and go the shortest route, i.e., via the opposite , ACUTE smaller angle -- which can happen when there's only one [i.e, destination] keyframe. To go the 'long' way around, I assume I need an extra keyframe midway through, along the desired arc.)
Here's what I've got so far (which does get the graphic to the desired rotation, via the most acute angle):
#define DEGREES_TO_RADIANS(__ANGLE__) ((__ANGLE__) / 180.0 * M_PI)
...
[UIView beginAnimations:nil context:nil];
CGAffineTransform cgCTM = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(desiredEndingAngle));
[UIView setAnimationDuration:0.5];
graphic.transform = cgCTM;
[UIView commitAnimations];
As I understand it, I’m not looking for animation along a Path (since that’s for Translation, rather than Rotation) ...
Anyway, any help would be VERY much appreciated! Thanks in advance.
Think I’ve got it.
Here’s code which does (in this example) a full 270 DEGREE rotation (1.5*pi radians), including various parameters that can be customized further:
CALayer *layer = rotatingImage.layer;
CAKeyframeAnimation *animation;
animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.duration = 0.5f;
animation.cumulative = YES;
animation.repeatCount = 1;
animation.values = [NSArray arrayWithObjects: // i.e., Rotation values for the 3 keyframes, in RADIANS
[NSNumber numberWithFloat:0.0 * M_PI],
[NSNumber numberWithFloat:0.75 * M_PI],
[NSNumber numberWithFloat:1.5 * M_PI], nil];
animation.keyTimes = [NSArray arrayWithObjects: // Relative timing values for the 3 keyframes
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:.5],
[NSNumber numberWithFloat:1.0], nil];
animation.timingFunctions = [NSArray arrayWithObjects:
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn], // from keyframe 1 to keyframe 2
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut], nil]; // from keyframe 2 to keyframe 3
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[layer addAnimation:animation forKey:nil];
Thanks!
Try this:
UIImageView* rotatingImage = [[UIImageView alloc] init]];
[rotatingImage setImage:[UIImage imageNamed:#"someImage.png"]];
CATransform3D rotationTransform = CATransform3DMakeRotation(1.0f * M_PI, 0, 0, 1.0);
CABasicAnimation* rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
rotationAnimation.toValue = [NSValue valueWithCATransform3D:rotationTransform];
rotationAnimation.duration = 0.25f;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = 1;
[rotatingImage.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 100, 100);
CGPathAddQuadCurveToPoint(path, NULL, 100, 100, 100, 615);
CGPathAddQuadCurveToPoint(path, NULL, 100, 615, 900, 615);
CGPathAddQuadCurveToPoint(path, NULL, 900, 615, 900, 100);
CGPathAddQuadCurveToPoint(path, NULL, 900, 100, 100, 80);
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.path = path;
pathAnimation.duration = 10.0;
[someLayer addAnimation:pathAnimation forKey:nil];

How can I enforce an specific direction (i.e. clockwise) of rotation in Core Animation?

I am rotating a view with this:
CGAffineTransform rotatedTransform = CGAffineTransformRotate(CGAffineTransformIdentity, rotationValue);
I have an object which I want to spin around for about 320 degrees. Now Core Animation is clever and just rotates it as much as needed, doing that by rotating it with -40 degrees. So the object rotates the other way around with a shorter amount of movement.
I want to constrain it to rotate clockwise. Would I have to do that by changing animations in little steps, or is there an more elegant way?
The following snippet rotates a view called someView by using key-framed animation. The animation consists of 3 frames spread over 1 second, with the view rotated to 0º, 180º and 360º in the first, second and last frames respectively. Code follows:
CALayer* layer = someView.layer;
CAKeyframeAnimation* animation;
animation = [CAKeyframeAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.duration = 1.0;
animation.cumulative = YES;
animation.repeatCount = 1;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
animation.values = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0 * M_PI],
[NSNumber numberWithFloat:0.5 * M_PI],
[NSNumber numberWithFloat:1.0 * M_PI], nil];
animation.keyTimes = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.5],
[NSNumber numberWithFloat:1.0], nil];
animation.timingFunctions = [NSArray arrayWithObjects:
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear], nil];
[layer addAnimation:animation forKey:#"transform.rotation.z"];
If you're after counterclockwise animation, you should use negative values. For a slightly more basic animation, you can use CABasicAnimation:
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"];
I believe you have to give it another "key frame" if you will to give Core Animation the hint that it needs to go that direction.
Make sure and turn off easing, (at least for the end/beginning of the middle step) otherwise the animation will not look smooth.