views:

1791

answers:

3

Hi,

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"];

}

A: 

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"];
}
Adrian Kosmaczewski
Thank you akosma!I have tried that way of scaling but I cant figure out how do add more than 2 keyframes. With 'setAutoreverses' it goes from A to B then back to A. I want to go from A to B to C to D.do you know if you can add more 'fromValues' and 'ToValues'?Thanks
Jonathan
Take a look at the CAAnimationGroup class. It inherits from CAAnimation, and with it you can "chain" individual CABasicAnimations, each with its own fromValue and toValue. Might this be what you are looking for?
Adrian Kosmaczewski
Scratch that, I've checked the docs and it appears that animations in a CAAnimationGroup are run concurrently, not one after the other. Bummer. You should set delegates for your animations, and then start a new animation when the previous one has finished.
Adrian Kosmaczewski
+3  A: 

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.

Brad Larson
Thanks Brad. This is exactly the kind of control I was after.
Jonathan
+4  A: 

Two alternative solutions for you:

First, You can also animate the transform property.

Using Brads 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:

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.tranform; 

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;

Second, and maybe 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

Corey Floyd
This works great... Thank you. I did add a .duration thou.I will check out FTUtilis - Thanks.
Jonathan