Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Animate CoreGraphics Drawing of Shape Using CAKeyframeAnimation

I am trying to animate the drawing of a UIBeizerPath (in my example a triangle) in a UIView subclass. However, the entire subview is animating instead of the shape.

Is there something I am missing with the animation?

- (void)drawRect:(CGRect)rect {

   CAShapeLayer *drawLayer = [CAShapeLayer layer];

   drawLayer.frame           = CGRectMake(0, 0, 100, 100);
   drawLayer.strokeColor     = [UIColor greenColor].CGColor;
   drawLayer.lineWidth       = 4.0;

   [self.layer addSublayer:drawLayer];

   UIBezierPath *path = [UIBezierPath bezierPath];

  [path moveToPoint:CGPointMake(0,0)];
  [path addLineToPoint:CGPointMake(50,100)];
  [path addLineToPoint:CGPointMake(100,0)];
  [path closePath];

  CGPoint center = [self convertPoint:self.center fromView:nil];

  [path applyTransform:CGAffineTransformMakeTranslation(center.x, center.y)];

  [[UIColor redColor] set];

  [path fill];

  [[UIColor blueColor] setStroke];

  path.lineWidth = 3.0f;

  [path stroke];

  CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];

  pathAnimation.duration        = 4.0f;
  pathAnimation.path            = path.CGPath;
  pathAnimation.calculationMode = kCAAnimationLinear;

  [drawLayer addAnimation:pathAnimation forKey:@"position"];
}
like image 757
Cory D. Wiles Avatar asked Mar 18 '12 20:03

Cory D. Wiles


1 Answers

  • You are creating a CAShapeLayer, but then not doing anything useful with it. Let's fix that.

  • Don't set up layers and animations in -drawRect:, because that's strictly meant as a time to do drawing using the CoreGraphics or UIKit APIs. Instead, you want the CAShapeLayer to draw the triangle -- that way you can animate it.

  • CAKeyframeAnimation.path is meant for something completely different (e.g. moving a layer along a path).

  • Your animation is animating the position value of the layer. No surprise that it moves the layer! You want to animate the path value instead.

  • The idea behind CAKeyframeAnimation is that you provide it an array of values to set the layer's property to. During the time between keyframes, it will interpolate between the two adjacent keyframes. So you need to give it several paths -- one for each side.

  • Interpolating arbitrary paths is difficult. CA's path interpolation works best when the paths have the same same number and kind of elements. So, we make sure all our paths have the same structure, just with some points on top of each other.

  • The secret to animation, and maybe to computers in general: you must be precise in explaining what you want to happen. "I want to animate the drawing of each point, so it appears to be animated" is not nearly enough information.

Here's a UIView subclass that I think does what you're asking for, or at least close. To animate, hook a button up to the -animate: action.

SPAnimatedShapeView.h:

#import <UIKit/UIKit.h>

@interface SPAnimatedShapeView : UIView

- (IBAction)animate:(id)sender;

@end

SPAnimatedShapeView.m:

#import "SPAnimatedShapeView.h"
#import <QuartzCore/QuartzCore.h>

@interface SPAnimatedShapeView ()
@property (nonatomic, retain)   CAShapeLayer*   shapeLayer;
@end

@implementation SPAnimatedShapeView

@synthesize shapeLayer = _shapeLayer;

- (void)dealloc
{
    [_shapeLayer release];
    [super dealloc];
}

- (void)layoutSubviews
{
    if (!self.shapeLayer)
    {
        self.shapeLayer = [[[CAShapeLayer alloc] init] autorelease];
        self.shapeLayer.bounds = CGRectMake(0, 0, 100, 100);     // layer is 100x100 in size
        self.shapeLayer.position = self.center;                  // and is centered in the view
        self.shapeLayer.strokeColor = [UIColor blueColor].CGColor;
        self.shapeLayer.fillColor = [UIColor redColor].CGColor;
        self.shapeLayer.lineWidth = 3.f;

        [self.layer addSublayer:self.shapeLayer];
    }
}

- (IBAction)animate:(id)sender
{
    UIBezierPath* path0 = [UIBezierPath bezierPath];
    [path0 moveToPoint:CGPointZero];
    [path0 addLineToPoint:CGPointZero];
    [path0 addLineToPoint:CGPointZero];
    [path0 addLineToPoint:CGPointZero];

    UIBezierPath* path1 = [UIBezierPath bezierPath];
    [path1 moveToPoint:CGPointZero];
    [path1 addLineToPoint:CGPointMake(50,100)];
    [path1 addLineToPoint:CGPointMake(50,100)];
    [path1 addLineToPoint:CGPointMake(50,100)];

    UIBezierPath* path2 = [UIBezierPath bezierPath];
    [path2 moveToPoint:CGPointZero];
    [path2 addLineToPoint:CGPointMake(50,100)];
    [path2 addLineToPoint:CGPointMake(100,0)];
    [path2 addLineToPoint:CGPointMake(100,0)];

    UIBezierPath* path3 = [UIBezierPath bezierPath];
    [path3 moveToPoint:CGPointZero];
    [path3 addLineToPoint:CGPointMake(50,100)];
    [path3 addLineToPoint:CGPointMake(100,0)];
    [path3 addLineToPoint:CGPointZero];

    CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"path"];    
    animation.duration = 4.0f;
    animation.values = [NSArray arrayWithObjects:(id)path0.CGPath, (id)path1.CGPath, (id)path2.CGPath, (id)path3.CGPath, nil];
    [self.shapeLayer addAnimation:animation forKey:nil];
}

@end
like image 147
Kurt Revis Avatar answered Nov 08 '22 07:11

Kurt Revis