2

In an iPhone app how would you animate the shape of an UIView, for example changing from a rectangle into a circle?

I've tried with:

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"path"];
animation.duration = 20.0;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fromValue = (__bridge_transfer id)aPath;
animation.toValue = (__bridge_transfer id)anotherPath;
[myShapeLayer addAnimation:animation forKey:@"animatePath"];

where myShapeLayer is an instance of CAShapeLayer and aPath and anotherPath CGMutablePathRef. It works but the view content is not animated as well.

I need to transform a view into a circle and then let it shrink until it disappears.

Amar
  • 13,202
  • 7
  • 53
  • 71
DAN
  • 919
  • 1
  • 6
  • 23

2 Answers2

3

Try something like this: Where animeView is your UIView

CABasicAnimation *anim1 = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
anim1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
anim1.fromValue = [NSNumber numberWithFloat:0.0f];
anim1.toValue = [NSNumber numberWithFloat:50.0f]; //Half the size of your UIView
anim1.duration = 2.0;
[animeView.layer addAnimation:anim1 forKey:@"cornerRadius"];

[UIView animateWithDuration:10.0 delay:2 options:UIViewAnimationOptionCurveEaseInOut animations:^{

    animeView.layer.cornerRadius = 50; //Half the size of your UIView
    CGRect reduceRect = animeView.frame;
    reduceRect.size.height = 0;
    reduceRect.size.width = 0;
    [animeView setFrame:reduceRect];
    animeView.alpha = 0;
                    } completion:nil];

Might need some tweaks for you here and there ;-)

EDIT 1:

Ok so how about using two UIView animations? The first will shrink, strech and move your view. The second will shrink, slink and remove your view.

[UIView animateWithDuration:2.0 delay:0.5 options:UIViewAnimationOptionCurveEaseIn animations:^{

    CGRect moveRect = animeView.frame;
    moveRect.origin.x = 0;
    moveRect.origin.y = (animeView.center.y -20); //Half the size of height reduction
    moveRect.size.height = (animeView.bounds.size.height -40); // height reduction
    moveRect.size.width = (animeView.bounds.size.width +20);
    [animeView setFrame:moveRect];

} completion:^(BOOL finished){
    [UIView animateWithDuration:2.0 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
        CGRect reduceRect = animeView.frame;
        reduceRect.size.height = 0;
        reduceRect.size.width = 0;
        reduceRect.origin.x = -50;
        reduceRect.origin.y = animeView.center.y;
        animeView.alpha = 0;
        [animeView setFrame:reduceRect];
    } completion:nil];

}];

EDIT 2:

A answer to you question in the comments:

You can execute animations simultaneous by creating a CAAnimationGroup. Also I'm using a image to create the resize of content effect. Example:

//Create a screenshot of your UIView... Still animeView in this example
UIGraphicsBeginImageContext(animeView.bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[animeView.layer renderInContext:context];
UIImage *screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

//Add the image as subview:
UIImageView * imageView = [[UIImageView alloc] initWithImage:screenShot];
[animeView addSubview:imageView];

//A cornerRadius animation:
CABasicAnimation *radiusAni = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
radiusAni.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
radiusAni.fromValue = [NSNumber numberWithFloat:0.0f];
radiusAni.toValue = [NSNumber numberWithFloat:50.0f];

//A stretch animation:
CABasicAnimation *stretchAni = [CABasicAnimation animationWithKeyPath:@"transform.scale.x"];
stretchAni.fromValue = [NSNumber numberWithDouble:1];
stretchAni.toValue = [NSNumber numberWithDouble:(animeView.frame.size.width+100)/animeView.frame.size.width];

//A slide animation:
CABasicAnimation *slideAni = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
slideAni.fromValue = [NSNumber numberWithDouble:0];
slideAni.toValue = [NSNumber numberWithDouble:-100];

//A opacity animation:
CABasicAnimation *opacityAni = [CABasicAnimation animationWithKeyPath:@"opacity"];
opacityAni.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
opacityAni.fromValue = [NSNumber numberWithFloat:1];
opacityAni.toValue = [NSNumber numberWithFloat:0];

//The animationgroup
CAAnimationGroup *animGroup = [CAAnimationGroup animation];

//Add them to the group:
[animGroup setAnimations:[NSArray arrayWithObjects:radiusAni, opacityAni, slideAni, stretchAni, nil]];
//Set the properties:
[animGroup setDuration:3.0];
[animGroup setRemovedOnCompletion:NO];
[animGroup setFillMode:kCAFillModeForwards];

//Execute all the animations in the group:
[animeView.layer addAnimation:animGroup forKey:nil];

Then you'll have 4 animations executing at the same time and the resize of the content when stretching, shrinking or whatever you plan to do ;-)

wkberg
  • 391
  • 2
  • 12
  • Hi wkberg, thanks for your quick answer! Actually I've simplified my task a bit too much, in fact the animation is quite more complex, and the morphing from a rectangle into a circle was just a (bad) example. I need to simulate an animation where a view, which has an arrow shape, is sucked into an hole. To achieve this I was thinking about creating a set of bezier curves which represent the view shape, and then let Quartz animate from one to another. The result should be very similar to the pull to refresh animation like mail in iOS6, but as said the view to be animated has some contents. – DAN Jul 12 '13 at 11:32
  • You're welcome ;-) So actually you're creating a hole. stretching your arrow towards it. Move it to the hole as `the suction` where it is `sucked in`. As in moving towards the hole and then removed or just not to be seen? – wkberg Jul 12 '13 at 11:47
  • I've tried to sketch my idea, here's the result: http://img59.imageshack.us/img59/8690/t6au.jpg The holes are the circles on the left, whereas the views to be animated are the arrows on the right. Hope it's clear :) – DAN Jul 12 '13 at 12:12
  • Ok I've updated my answer: Then you only need to take care of shrinking the content inside your `arrowView`. – wkberg Jul 12 '13 at 13:03
  • If I'm right the two animations you wrote only take care of resizing the frame's size, which will remain a rectangle. I previously mentioned bezier curves in order to have the ability to increase the corners roundness (through control points) during the animation and achieve a kind of liquid/fluid effect, as I tried to depict in my sketch :) – DAN Jul 12 '13 at 13:12
  • Correct! So you're actually questioning the how to use bezier-curves instead? I suggest checking out [this.](http://www.raywenderlich.com/34003/core-graphics-tutorial-curves-and-layers) It is a tutorial about `Core Animation` and `bezier paths`. You then should be able to use the animation code in my answer and the creation of a `rect` with `bezier curves from the tutorial. ;-) – wkberg Jul 12 '13 at 13:37
  • Ok, I'm still missing what you mean with "creation of a rect with `bezier curves": a rect is a rectangle, how could its shape be a curve? – DAN Jul 12 '13 at 17:48
  • Check out the tutorial ;-) There they're creating a `CGRect` with mountains [and check this answer out](http://stackoverflow.com/questions/6434925/how-to-draw-uibezierpaths?answertab=active#tab-top) – wkberg Jul 12 '13 at 18:14
  • Yes, the tutorial is clear, the rect remains a rectangle but I can override the drawRect method in order to draw a shape with bezier curves. Therefore I need to animate two things: 1) the view with [UIView animateWithDuration...] so that all the subviews get shrinked, streched and moved as well; 2) the curve with CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"path"] so that the view shape changes too. So my final question is: how can they be simultaneous? :) – DAN Jul 13 '13 at 11:00
  • You should be able to create all animations with CABasicAnimation. Then use the EDIT 2 I've posted to make them all simultaneous and added a way to resize your content of the view as well ;-) – wkberg Jul 13 '13 at 18:00
0

If you want to animate changes in shape you could use a CAShapeLayer. That takes a CGPath that describes the shape to be drawn. It can only draw 1 path, and that drawing can only use a single line color and a single fill color. You can't draw some lines in color 1 and other lines in color 2, etc.

Once you have a CAShapeLayer you can animate changes to the CGPath object associated with the shape layer. The trick to making it work is that the starting and ending CGPath need to have the same number of control points. If you use a path that has 3 linked bezier paths, the end path needs to use 3 linked bezier paths also, but you can move the points around as much as you want.

I've found through experimentation that you can't animate changes to arcs in bezier curves. The reason is that internally, the system generates a series of bezier curves to create the arc, and there are more bezier segments the larger the arc angle.

Duncan C
  • 128,072
  • 22
  • 173
  • 272