CAKeyframeAnimation animation starts without the first frame(path) - ios

I want to animate the mask with different bezier shapes. My animation starts but I cannot see the first path. The code is below:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
_shapeLayer = [CAShapeLayer layer];
[self drawParentLayer];
}
- (void)drawParentLayer {
_shapeLayer.frame = CGRectMake(0, 0, 350, 500);
_shapeLayer.lineWidth = 3;
_shapeLayer.strokeColor = [UIColor blackColor].CGColor;
_shapeLayer.fillColor = UIColor.whiteColor.CGColor;
_shapeLayer.path = [UIBezierPath bezierPathWithRect:_shapeLayer.bounds].CGPath;
_shapeLayer.masksToBounds = NO;
[self.view.layer addSublayer:_shapeLayer];
}
Then I create my mask layer but in the location of touch.
How to get touch point:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.view];
CGPoint locationInLayer = CGPointMake(location.x - _shapeLayer.frame.origin.x, location.y - _shapeLayer.frame.origin.y);
_shapeLayer.fillColor = UIColor.yellowColor.CGColor;
[self createBezierLayerInPoint:locationInLayer];
}
Bezier layer in point of touch:
- (void)createBezierLayerInPoint:(CGPoint)point {
CAShapeLayer *layer = [CAShapeLayer layer];
layer.fillColor = UIColor.whiteColor.CGColor;
layer.shadowColor = [[UIColor whiteColor] CGColor];
layer.shadowOffset = CGSizeMake(0.0, 0.0);
layer.shadowOpacity = 0.8;
layer.shadowRadius = 20.0;
layer.lineWidth = 0;
UIBezierPath *bezier = [BezierObject createBezierObject1];
layer.bounds = bezier.bounds;
layer.path = bezier.CGPath;
layer.position = point;
// my bezier objects are big so I decided to scale
layer.transform = CATransform3DMakeScale(0.2, 0.2, 1);
_shapeLayer.mask = layer;
[self animate:layer];
}
The pictures from bezier1 to bezier3 are next:
Animation of mask(bezier) layer:
- (void)animate:(CAShapeLayer *)layer {
CAKeyframeAnimation *frameA = [CAKeyframeAnimation animation];
[frameA setKeyPath:#"path"];
frameA.values = #[(id)[BezierObject createBezierObject1].CGPath, (id)[BezierObject createBezierObject2].CGPath, (id)[BezierObject createBezierObject3].CGPath];
frameA.keyTimes = #[#0, #(1/3), #1];
frameA.additive = YES;
frameA.removedOnCompletion = NO;
frameA.fillMode = kCAFillModeForwards;
frameA.duration = 3;
[layer addAnimation:frameA forKey:#"path"];
}
As I mentioned before animation starts from the object2. So, maybe it's just simple mistake but I can't see it.
Thank you!

The problem is the second value in your keyTimes array. Since nominator and denominator are integers, the result is zero. You should force a floating point division like this:
frameA.keyTimes = #[#0.0, #(1.0/3.0), #1.0];

Related

CAShapeLayer animation doesn't stay on screen but disappears

I'm trying to draw a animated circle but every segment needs to have another color. Now everything works except that my piece that is just drawed before I call the method again disappears so only the last part stays. I don't want that, I want that after 4 times a whole circle is drawn in different color strokes.
How can I fix this that it doesn't disappears?
This is my init code:
- (void)_initCircle {
_circle = [CAShapeLayer layer];
_radius = 100;
// Make a circular shape
_circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0 * _radius, 2.0 * _radius) cornerRadius:_radius].CGPath;
// Center the shape in self.view
_circle.position = CGPointMake(CGRectGetMidX(self.view.frame) - _radius,
CGRectGetMidY(self.view.frame) - _radius);
_circle.fillColor = [UIColor clearColor].CGColor;
_circle.lineWidth = 5;
// Add to parent layer
[self.view.layer addSublayer:_circle];
}
This is my draw piece:
- (void)_drawStrokeOnCircleFrom:(float)start to:(float)end withColor:(UIColor *)color {
// Configure the apperence of the circle
_circle.strokeColor = color.CGColor;
_circle.strokeStart = start;
_circle.strokeEnd = end;
[CATransaction begin];
// Configure animation
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 2.0; // "animate over 10 seconds or so.."
drawAnimation.repeatCount = 1.0; // Animate only once..
// Animate from no part of the stroke being drawn to the entire stroke being drawn
drawAnimation.fromValue = [NSNumber numberWithFloat:start];
drawAnimation.toValue = [NSNumber numberWithFloat:end];
// Experiment with timing to get the appearence to look the way you want
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[CATransaction setCompletionBlock:^{
NSLog(#"Complete animation");
_index++;
if(_index < _votes.count){
_startStroke = _endStroke;
_endStroke += [_votes[_index] floatValue] / _totalListens;
[self _drawStrokeOnCircleFrom:_startStroke to:_endStroke withColor:_colors[_index]];
}
}];
// Add the animation to the circle
[_circle addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
[CATransaction commit];
}
Try setting these values on your CABasicAnimation:
drawAnimation.removedOnCompletion = NO;
drawAnimation.fillMode = kCAFillModeForwards;
That worked for me using "kCAFillModeBoth", both statements are necessary:
//to keep it after finished
animation.removedOnCompletion = false;
animation.fillMode = kCAFillModeBoth;
I solved the problem with extra layers like this:
- (void)_drawStrokeOnCircle {
[self _initCircle];
[CATransaction begin];
for (NSUInteger i = 0; i < 4; i++)
{
CAShapeLayer* strokePart = [[CAShapeLayer alloc] init];
strokePart.fillColor = [[UIColor clearColor] CGColor];
strokePart.frame = _circle.bounds;
strokePart.path = _circle.path;
strokePart.lineCap = _circle.lineCap;
strokePart.lineWidth = _circle.lineWidth;
// Configure the apperence of the circle
strokePart.strokeColor = ((UIColor *)_colors[i]).CGColor;
strokePart.strokeStart = [_segmentsStart[i] floatValue];
strokePart.strokeEnd = [_segmentsEnd[i] floatValue];
[_circle addSublayer: strokePart];
// Configure animation
CAKeyframeAnimation* drawAnimation = [CAKeyframeAnimation animationWithKeyPath: #"strokeEnd"];
drawAnimation.duration = 4.0;
// Animate from no part of the stroke being drawn to the entire stroke being drawn
NSArray* times = #[ #(0.0),
#(strokePart.strokeStart),
#(strokePart.strokeEnd),
#(1.0) ];
NSArray* values = #[ #(strokePart.strokeStart),
#(strokePart.strokeStart),
#(strokePart.strokeEnd),
#(strokePart.strokeEnd) ];
drawAnimation.keyTimes = times;
drawAnimation.values = values;
drawAnimation.removedOnCompletion = NO;
drawAnimation.fillMode = kCAFillModeForwards;
// Experiment with timing to get the appearence to look the way you want
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[strokePart addAnimation: drawAnimation forKey: #"drawCircleAnimation"];
}
[CATransaction commit];
}
- (void)_initCircle {
[_circle removeFromSuperlayer];
_circle = [CAShapeLayer layer];
_radius = 100;
// Make a circular shape
_circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0 * _radius, 2.0 * _radius) cornerRadius:_radius].CGPath;
// Center the shape in self.view
_circle.position = CGPointMake(CGRectGetMidX(self.view.frame) - _radius,
CGRectGetMidY(self.view.frame) - _radius);
_circle.fillColor = [UIColor clearColor].CGColor;
_circle.lineWidth = 5;
// Add to parent layer
[self.view.layer addSublayer:_circle];
}
SWIFT 4, Xcode 11:
let strokeIt = CABasicAnimation(keyPath: "strokeEnd")
let timeLeftShapeLayer = CAShapeLayer()
and in your viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
drawTimeLeftShape()
strokeIt.fillMode = CAMediaTimingFillMode.forwards
strokeIt.isRemovedOnCompletion = false
strokeIt.fromValue = 0
strokeIt.toValue = 1
strokeIt.duration = 2
timeLeftShapeLayer.add(strokeIt, forKey: nil)
}
and by UIBezierPathcreate function to draw circle:
func drawTimeLeftShape() {
timeLeftShapeLayer.path = UIBezierPath(
arcCenter: CGPoint(x: myView.frame.midX , y: myView.frame.midY),
radius: myView.frame.width / 2,
startAngle: -90.degreesToRadians,
endAngle: 270.degreesToRadians,
clockwise: true
).cgPath
timeLeftShapeLayer.strokeColor = UIColor.red.cgColor
timeLeftShapeLayer.fillColor = UIColor.clear.cgColor
timeLeftShapeLayer.lineWidth = 1
timeLeftShapeLayer.strokeEnd = 0.0
view.layer.addSublayer(timeLeftShapeLayer)
}

iOS drag shape pinned to center

Taking a next step from this post Drag UIView around Shape Comprised of CGMutablePaths , I am trying to add a line which is on one end pinned to the center of the pathLayer_ and the other end gets dragged along with the circular object (circleLayer in handleView_) on the path (figure-8).
The line has its own layer andview and path initiated in (void)initHandleView.
First: I am not able to get the line to go through the center of the pathLayer_:
- (void)viewDidLoad {
[super viewDidLoad];
[self initPathLayer];
[self initHandleView];
[self initHandlePanGestureRecognizer];
}
- (void)initPathLayer {
pathLayer_ = [CAShapeLayer layer];
pathLayer_.lineWidth = 1;
pathLayer_.fillColor = nil;
pathLayer_.strokeColor = [UIColor lightGrayColor].CGColor;
pathLayer_.lineCap = kCALineCapButt;
pathLayer_.lineJoin = kCALineJoinRound;
pathLayer_.frame = self.view.bounds;
pathLayerCenter_ = CGPointMake(CGRectGetMidX(pathLayer_.frame), CGRectGetMidY(pathLayer_.frame));
[self.view.layer addSublayer:pathLayer_];
}
- (void)initHandleView {
handlePathPointIndex_ = 0;
CGRect rect = CGRectMake(0, 0, 30, 30);
CAShapeLayer *circleLayer = [CAShapeLayer layer];
circleLayer.fillColor = [UIColor redColor].CGColor;
circleLayer.strokeColor = [UIColor redColor].CGColor;
circleLayer.lineWidth = 2;
UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(rect, circleLayer.lineWidth, circleLayer.lineWidth)];
circleLayer.frame = rect;
circleLayer.path = circlePath.CGPath;
handleView_ = [[UIView alloc] initWithFrame:rect];
CGRect lineRect = CGRectMake(0,0,CGRectGetMaxX([[UIScreen mainScreen] bounds]), CGRectGetMaxY([[UIScreen mainScreen] bounds]));
CGPoint center = CGPointMake(CGRectGetMidX(pathLayer_.frame), CGRectGetMidY(pathLayer_.frame));
lineView_ = [[UIView alloc] initWithFrame:lineRect];
CAShapeLayer *lineLayer = [CAShapeLayer layer];
lineLayer.fillColor = [UIColor blueColor].CGColor;
lineLayer.strokeColor = [UIColor blueColor].CGColor;
lineLayer.lineWidth = 2;
lineLayer.frame = self.view.bounds;
UIBezierPath *linePath = [UIBezierPath bezierPath];
CGPoint circlePoint = CGPointMake(handleView_.center.x, handleView_.center.y);
[linePath moveToPoint:center]; //Center
[linePath addLineToPoint:circlePoint];
[linePath moveToPoint:circlePoint]; //Handle
lineLayer.path = linePath.CGPath;
[handleView_.layer insertSublayer:circleLayer above:handleView_.layer];
[handleView_.layer insertSublayer:lineLayer above:handleView_.layer];
// handleView_.layer.masksToBounds = YES;
float direction = DEGREES_TO_RADIANS(10);
CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(direction);
[handleView_ setTransform:rotationTransform];
[self.view addSubview:lineView_];
[self.view addSubview:handleView_];
}
And second: I am not sure I do the right thing with the PanGesturerecognizer:
- (void)handleWasPanned:(UIPanGestureRecognizer *)recognizer {
switch (recognizer.state) {
case UIGestureRecognizerStateBegan: {
desiredHandleCenter_ = handleView_.center;
break;
}
case UIGestureRecognizerStateChanged:
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
CGPoint translation = [recognizer translationInView:self.view];
desiredHandleCenter_.x += translation.x;
desiredHandleCenter_.y += translation.y;
[self moveHandleTowardPointAndRotateLine:desiredHandleCenter_];
break;
}
default:
break;
}
[recognizer setTranslation:CGPointZero inView:self.view];
}
- (void)moveHandleTowardPointAndRotateLine:(CGPoint)point {
CGFloat earlierDistance = [self distanceToPoint:point ifHandleMovesByOffset:-1];
CGFloat currentDistance = [self distanceToPoint:point ifHandleMovesByOffset:0];
CGFloat laterDistance = [self distanceToPoint:point ifHandleMovesByOffset:1];
if (currentDistance <= earlierDistance && currentDistance <= laterDistance)
return;
NSInteger step;
CGFloat distance;
if (earlierDistance < laterDistance) {
step = -1;
distance = earlierDistance;
} else {
step = 1;
distance = laterDistance;
}
NSInteger offset = step;
while (true) {
NSInteger nextOffset = offset + step;
CGFloat nextDistance = [self distanceToPoint:point ifHandleMovesByOffset:nextOffset];
if (nextDistance >= distance)
break;
distance = nextDistance;
offset = nextOffset;
}
handlePathPointIndex_ += offset;
// Make one end of the line move with handle (point) while the other is pinned to center of pathLayer_ (ie. in figure8 its the cross point, in a circle it's center)
//CGFloat rot = [self getTouchAngle:point];
CGFloat rot = atan2f((point.x - pathLayerCenter_.x), -(point.y - pathLayerCenter_.y));
handleView_.layer.transform = CATransform3DMakeRotation(rot, 0., 0., 1.);
[self layoutHandleView];
}
I'm just going to ignore your code and tell you how I'd modify my original answer to add the line you want.
First, I'm going to use a dedicated shape layer for the line, so I'll add an instance variable to reference the line layer:
#implementation ViewController {
CAShapeLayer *lineLayer_; // NEW!!!
UIBezierPath *path_;
CAShapeLayer *pathLayer_;
etc.
I need to initialize the line layer when I load the view:
- (void)viewDidLoad {
[super viewDidLoad];
[self initPathLayer];
[self initHandleView];
[self initHandlePanGestureRecognizer];
[self initLineLayer]; // NEW!!!
}
- (void)initLineLayer {
lineLayer_ = [CAShapeLayer layer];
lineLayer_.lineWidth = 1;
lineLayer_.fillColor = nil;
lineLayer_.strokeColor = [UIColor redColor].CGColor;
lineLayer_.lineCap = kCALineCapRound;
lineLayer_.lineJoin = kCALineJoinRound;
[self.view.layer addSublayer:lineLayer_];
}
I need to lay out the line layer when I lay out my top-level view (when it's first shown and on rotations):
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self createPath];
[self createPathPoints];
[self layoutPathLayer];
[self layoutHandleView];
[self layoutLineLayer]; // NEW!!!
}
- (void)layoutLineLayer {
lineLayer_.frame = self.view.bounds;
CGRect bounds = self.view.bounds;
CGPoint start = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
CGPoint end = pathPoints_[handlePathPointIndex_];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:start];
[path addLineToPoint:end];
lineLayer_.path = path.CGPath;
}
Finally, I need to lay out the line layer again (to update its path) whenever I update the handle:
- (void)moveHandleTowardPoint:(CGPoint)point {
// I have omitted most of this method body for brevity.
// Just copy it from the original answer.
...
...
handlePathPointIndex_ += offset;
[self layoutHandleView];
[self layoutLineLayer]; // NEW!!!
}
Result:

Animate a CAShapeLayer to draw a progress circle

I am trying to draw a circle that will be used to indicate progress. The progress updates might come in quickly, and I want to animate the changes to the circle.
I have tried doing this with the below methods, but nothing seems to work. Is this possible?
- (id)initWithFrame:(CGRect)frame strokeWidth:(CGFloat)strokeWidth insets:(UIEdgeInsets)insets
{
if (self = [super initWithFrame:frame])
{
self.strokeWidth = strokeWidth;
CGPoint arcCenter = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGFloat radius = CGRectGetMidX(self.bounds) - insets.top - insets.bottom;
self.circlePath = [UIBezierPath bezierPathWithArcCenter:arcCenter
radius:radius
startAngle:M_PI
endAngle:-M_PI
clockwise:YES];
[self addLayer];
}
return self;
}
- (void)setProgress:(double)progress {
_progress = progress;
[self updateAnimations];
}
- (void)addLayer {
CAShapeLayer *progressLayer = [CAShapeLayer layer];
progressLayer.path = self.circlePath.CGPath;
progressLayer.strokeColor = [[UIColor whiteColor] CGColor];
progressLayer.fillColor = [[UIColor clearColor] CGColor];
progressLayer.lineWidth = self.strokeWidth;
progressLayer.strokeStart = 10.0f;
progressLayer.strokeEnd = 40.0f;
[self.layer addSublayer:progressLayer];
self.currentProgressLayer = progressLayer;
}
- (void)updateAnimations{
self.currentProgressLayer.strokeEnd = self.progress;
[self.currentProgressLayer didChangeValueForKey:#"endValue"];
}
Currently this code doesn't even draw the circle, but removing the progressLayer's strokeStart and strokeEnd will draw the full circle.
How can I get it to begin at a certain point and start drawing the circle based on me setting my progress property?
I figured it out. The strokeStart and strokeEnd values are from 0.0 to 1.0, so the values I was giving it were way too high.
So, I just updated my setting to:
- (void)setProgress:(double)progress {
_progress = progress * 0.00460;
[self updateAnimations];
}
My answer with example here. In general you should add the animation to CAShapeLayer.

CAEmitterLayer animation that leaves accurate trail along the path

I need to leave a trail with UIView that is animated with CAKeyframeAnimation
Ball * ball = [[Ball alloc]init];
// customized the ball
CGMutablePathRef path = CGPathCreateMutable();
// filling in the path from points.
CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:#"position"];
anim.path = path;
anim.rotationMode = kCAAnimationRotateAuto;
anim.repeatCount = HUGE_VALF;
anim.duration = 1.2;
anim.cumulative = YES;
anim.additive = YES;
[ball.layer addAnimation:anim forKey:#"fly"];
Now the Ball.m will have the following Emitter layer:
#implementation TennisBall{
__weak CAEmitterLayer *emitterLayer;
}
+ (Class)layerClass
{
return [CAEmitterLayer class];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setupLayer];
emitterLayer = [CAEmitterLayer layer];
emitterLayer.frame = self.bounds;
emitterLayer.emitterPosition = CGPointMake(160, 240);
emitterLayer.emitterSize = CGSizeMake(10, 10);
emitterLayer.renderMode = kCAEmitterLayerAdditive;
CAEmitterCell *emitterCell = [CAEmitterCell emitterCell];
emitterCell.birthRate = 1;
emitterCell.lifetime = 10.0;
emitterCell.lifetimeRange = 0.5;
emitterCell.velocity = 20;
emitterCell.velocityRange = 10;
emitterCell.emissionRange = 0;
emitterCell.scaleSpeed = 0.3;
emitterCell.spin = 0;
emitterCell.color = [[UIColor colorWithRed:0.8 green:0.4 blue:0.2 alpha:0.1]
CGColor];
emitterCell.contents = (id)[[UIImage imageNamed:#"first.png"] CGImage];
[emitterCell setName:#"fire"];
emitterLayer.emitterCells = [NSArray arrayWithObject:emitterCell];
[self.layer addSublayer:emitterLayer];
}
return self;
}
I just need the ball to leave a trail that repeats the path that the ball travels. How do I achieve that?
To make sure the emitter position is correct it the ball layer,
You need to make sure emitterPosition same as its anchorPoint.
For example, if anchorPoint is (0,0), then emitterPosition is (0,0).
If anchorPoint is (0.5,0.5), and size is (0,0,300,300) then emitterPosition is (150,150)

CAShapeLayer animation (arc from 0 to final size)

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

Resources