CAShapeLayer animation (arc from 0 to final size)
Asked Answered
N

3

10

I have a CAShapeLayer in which an arc is added using UIBezierPath. I saw a couple of posts (one actually) here on stackoverflow but none seemed to give me the answer. As said in the title I would like to animate an arc (yes it will be for a pie chart).

How can one accomplish an animation from an "empty" arc to the fully extended one? Kinda like a curved progress bar. Is it really impossible to do it via CoreAnimation?

Here's how I "do" the arc. Oh and ignore the comments and calculation since I'm used to counter clockwise unit circle and apple goes clockwise. The arcs appear just fine, I only need animation:

    //Apple's unit circle goes in clockwise rotation while im used to counter clockwise that's why my angles are mirrored along x-axis
    workAngle = 6.283185307 - DEGREES_TO_RADIANS(360 * item.value / total);

    //My unit circle, that's why negative currentAngle
    [path moveToPoint:CGPointMake(center.x + cosf(outerPaddingAngle - currentAngle) * (bounds.size.width / 2), center.y - (sinf(outerPaddingAngle - currentAngle) * (bounds.size.height / 2)))];

    //Apple's unit circle, clockwise rotation, we add the angles
    [path addArcWithCenter:center radius:120 startAngle:currentAngle endAngle:currentAngle + workAngle clockwise:NO];

    //My unit circle, counter clockwise rotation, we subtract the angles
    [path addLineToPoint:CGPointMake(center.x + cosf(-currentAngle - workAngle) * ((bounds.size.width / 2) - pieWidth), center.y - sinf(-currentAngle - workAngle) * ((bounds.size.height / 2) - pieWidth))];

    //Apple's unit circle, clockwise rotation, addition of angles
    [path addArcWithCenter:center radius:120 - pieWidth startAngle:currentAngle + workAngle endAngle:currentAngle - innerPaddingAngle clockwise:YES];

    //No need for my custom calculations since I can simply close the path
    [path closePath];

    shape = [CAShapeLayer layer];
    shape.frame = self.bounds;
    shape.path = path.CGPath;
    shape.fillColor = kRGBAColor(255, 255, 255, 0.2f + 0.1f * (i + 1)).CGColor; //kRGBAColor is a #defined

    [self.layer addSublayer:shape];


    //THIS IS THE PART IM INTERESTED IN
    if (_shouldAnimate)
    {
        CABasicAnimation *pieAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
        pieAnimation.duration = 1.0;
        pieAnimation.removedOnCompletion = NO;
        pieAnimation.fillMode = kCAFillModeForwards;
        //from and to are just dummies so I dont get errors
        pieAnimation.fromValue = [NSNumber numberWithInt:0]; //from what
        pieAnimation.toValue = [NSNumber numberWithFloat:1.0f]; //to what

        [shape addAnimation:pieAnimation forKey:@"animatePath"];
    }
Ninos answered 16/9, 2013 at 19:36 Comment(2)
Try this https://mcmap.net/q/570681/-how-to-animate-cashapelayer-path-and-fillcolorBeniamino
No good, this one animates the border so that the piece draws itself and then fills up with colour (all over the fill area). I want it to grow like a progress bar.Ninos
N
7

Here is an implementation of a CALayer based on CAShapeLayer:

ProgressLayer.h/m

#import <QuartzCore/QuartzCore.h>

@interface ProgressLayer : CAShapeLayer

-(void) computePath:(CGRect)r;
-(void) showProgress;
@end

#import "ProgressLayer.h"

@implementation ProgressLayer
-(id)init {
    self=[super init];
    if (self) {
        self.path = CGPathCreateWithEllipseInRect(CGRectMake(0, 0, 50, 50), 0);
        self.strokeColor = [UIColor greenColor].CGColor;
        self.lineWidth=40;
        self.lineCap = kCALineCapRound;
        self.strokeEnd=0.001;
    }
    return self;
}

-(void) computePath:(CGRect)r {
    self.path = CGPathCreateWithEllipseInRect(r, 0);
}


-(void)showProgress {
    float advance=0.1;
    if (self.strokeEnd>1) self.strokeEnd=0;

    CABasicAnimation * swipe = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    swipe.duration=0.25;
    swipe.fromValue=[NSNumber numberWithDouble:self.strokeEnd];
    swipe.toValue=  [NSNumber numberWithDouble:self.strokeEnd + advance];

    swipe.fillMode = kCAFillModeForwards;
    swipe.timingFunction= [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    swipe.removedOnCompletion=NO;
    self.strokeEnd = self.strokeEnd + advance;
    [self addAnimation:swipe forKey:@"strokeEnd animation"];
}

@end

I used this layer as a backing layer of an UIView:

#import "ProgressView.h"
#import "ProgressLayer.h"

@implementation ProgressView


-(id)initWithCoder:(NSCoder *)aDecoder {
    self=[super initWithCoder:aDecoder];
    if (self) {
        [(ProgressLayer *)self.layer computePath:self.bounds];
    }
    return self;
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    ProgressLayer * l =  self.layer;
    [l showProgress];
}


- (void)drawRect:(CGRect)rect {
    [[UIColor redColor] setStroke];
    UIRectFrame(self.bounds);
}


+(Class)layerClass {
    return [ProgressLayer class];
}

@end

initial state

animation in progress/0

animation in progress/1

Norenenorfleet answered 28/9, 2013 at 11:2 Comment(2)
can we change somehow fill color? call [path fill] does not do the trickInterpretative
Seee CAShapeLayer fillColor property.Norenenorfleet
R
3

Before you create your animation you want to set your shape's strokeEnd to 0.

Then, use the key @"strokeEnd" instead of @"path"... Keys for CAAnimations are generally the name of the property you want to animate.

Then you can do the following:

[CATransaction begin]
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
animation.duration = 1.0f;
animation.removedOnCompletion = NO;
animation.fromValue = @0; //shorthand for creating an NSNumber
animation.toValue = @1; //shorthand for creating an NSNumber
[self addAnimation:animation forKey:@"animateStrokeEnd"];
[CATransaction commit];
Roubaix answered 28/9, 2013 at 8:5 Comment(0)
W
2

You have to use the proper keys. "path" and "animatePath" don't mean anything to it - you have to use "strokeEnd" for your key/keypath. If you need to fiddle with your fill color, you use fillColor. The full list is here:

https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CAShapeLayer_class/Reference/Reference.html

Wina answered 16/9, 2013 at 20:14 Comment(3)
Yes I know I have to use proper keys, I just don't know which key to use since I want a "curved progress bar" fill animation.Ninos
@Ninos it'll almost certainly be strokeEnd. Create a path that is the curve you want, set it to stroke with whatever width gives you the bar you want, then animate stroke end. When that property is 0 the layer will draw the first 0% of your path. When it's 0.5 the layer will draw the first 50% of your path, etc.Depredation
Well not really. Stroke animates the outer line of the shape. I want the entire shape to grow up - including fill. You did pop an idea in my head when you wrote this post - I tried so set lineWidth to 50 and make the actual fill area really tiny so that it was almost invisible but then the outer and inner corner weren't aligned. Hope you understand what I mean. If you do have a solution please post an answer including some code so that I can see where I went wrong. Thanks!Ninos

© 2022 - 2024 — McMap. All rights reserved.