Draw circle animation with rounded stroke in iOS - ios

I'm trying to animate a circle being drawn in iOS 7 - which is quite easy.
My problem is that I need the stroke to have rounded ends. The way I'm trying to do that now is by adding another circle to the start position of the animation.
Then for the end that is moving, I need another circle to follow along. It's doing pretty much what I want, but I need it to use an easeInOutQuart timing which is turning out more difficult than I thought.
The result so far is this:
My code looks like this:
- (void) drawCircleAnimated {
int radius = 100;
CALayer *animationLayer = [CALayer layer];
animationLayer.frame = CGRectMake(20.0f, 64.0f, CGRectGetWidth(self.view.layer.bounds) - 40.0f, CGRectGetHeight(self.view.layer.bounds) - 84.0f);
[self.view.layer addSublayer:animationLayer];
CGRect pathRect = CGRectInset(animationLayer.bounds, 100.0f, 100.0f);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius) cornerRadius:radius];
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.frame = animationLayer.bounds;
pathLayer.bounds = pathRect;
pathLayer.geometryFlipped = NO;
pathLayer.path = path.CGPath;
pathLayer.strokeColor = [[UIColor blackColor] CGColor];
pathLayer.fillColor = nil;
pathLayer.lineWidth = 10.0f;
pathLayer.lineJoin = kCALineJoinBevel;
[animationLayer addSublayer:pathLayer];
[self addStartPointCircle];
CAShapeLayer* circleToMove = [self addStartPointCircle];
circleToMove.anchorPoint = CGPointZero;
[pathLayer addSublayer:circleToMove];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = 5.0;
pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
[pathLayer addAnimation:pathAnimation forKey:#"strokeEnd"];
CAKeyframeAnimation *penAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
penAnimation.duration = 5.0;
penAnimation.path = pathLayer.path;
penAnimation.calculationMode = kCAAnimationPaced;
penAnimation.delegate = self;
penAnimation.removedOnCompletion = NO;
[circleToMove addAnimation:penAnimation forKey:#"position"];
}
To achieve the easing function, I've added this to the penAnimation - but it doesn't change anything. I guest it will always follow the other animation?
penAnimation.values = [NSArray arrayWithObjects: // i.e., Rotation values for the 3 keyframes, in RADIANS
[NSNumber numberWithFloat:0.0 * M_PI],
[NSNumber numberWithFloat:0.75 * M_PI],
[NSNumber numberWithFloat:1.5 * M_PI], nil];
penAnimation.keyTimes = [NSArray arrayWithObjects: // Relative timing values for the 3 keyframes
[NSNumber numberWithFloat:0],
[NSNumber numberWithFloat:.5],
[NSNumber numberWithFloat:1.0], nil];
penAnimation.timingFunctions = [NSArray arrayWithObjects:
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn], // from keyframe 1 to keyframe 2
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut], nil]; // from keyframe 2 to keyframe 3

This should be all you need to add.
pathLayer.lineCap = kCALineCapRound;

Related

How can I make a circular progress meter with using own picture?

I want to make a meter like this picture.
I have 2 meter images - color meter image and gray meter image.
And I want to make a mater as shown in the above image by using them.
But I don't have any idea.
I got some sample code for making circular meter and filling some color in it. Here is that code:
CAShapeLayer *circle=[CAShapeLayer layer];
circle.path=[UIBezierPath bezierPathWithArcCenter:CGPointMake(29, 29) radius:27 startAngle:2*M_PI*0-M_PI_2 endAngle:2*M_PI*1-M_PI_2 clockwise:YES].CGPath;
circle.fillColor=[UIColor clearColor].CGColor;
circle.strokeColor=[UIColor greenColor].CGColor;
circle.lineWidth=4;
CABasicAnimation *animation=[CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.duration=10;
animation.removedOnCompletion=NO;
animation.fromValue=#(0);
animation.toValue=#(1);
animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[circle addAnimation:animation forKey:#"drawCircleAnimation"];
[imageCircle.layer.sublayers makeObjectsPerformSelector:#selector(removeFromSuperlayer)];
[imageCircle.layer addSublayer:circle];
If you have an idea,please share it with me.
(it is ok that your idea uses this cord or not)
Thank you.
Here's a fairly robust example, just use this as a template:
video show: https://www.youtube.com/watch?v=ucYqb0Gs1_8&feature=youtu.be
just call to the second method, and the second method will call the first method, and there you have it. Drop it into a view controllers view and you will see the magic happen:
#define DEGREES_TO_RADIANS(degrees) ((M_PI * degrees)/ 180)
-(void)this
{
[CATransaction begin];
[CATransaction setAnimationDuration:3.5];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
CABasicAnimation * drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.removedOnCompletion = YES;
drawAnimation.autoreverses = YES;
drawAnimation.repeatCount = INFINITY;
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:1.f];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[arc addAnimation:drawAnimation forKey:#"thisone"];
[CATransaction commit];
[CATransaction begin];
[CATransaction setAnimationDuration:3.5];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
CABasicAnimation *drawAnimation1 = [CABasicAnimation animationWithKeyPath:#"strokeStart"];
drawAnimation1.removedOnCompletion = YES;
drawAnimation1.autoreverses = YES;
drawAnimation1.repeatCount = INFINITY;
drawAnimation1.fromValue = [NSNumber numberWithFloat:0.0];
drawAnimation1.toValue = [NSNumber numberWithFloat:-1.0];
[arc addAnimation:drawAnimation1 forKey:#"myKey"];
[CATransaction commit];
}
-(void)doGradientoutline {
float th = 50.00;
UIView * hellowkitt = [UIView new];
[hellowkitt setFrame:CGRectMake(SCREEN_WIDTH/2-(30+th)*2/2-th, SCREEN_HEIGHT/4-th, (30+th)*2+th*2, (30+th)*2+th*2)];
[self.view addSubview: hellowkitt];
UIView * imageView = [UIView new];
[imageView setFrame:CGRectMake(th, th, hellowkitt.frame.size.width-th*2, hellowkitt.frame.size.height-th*2)];
[imageView setClipsToBounds:true];
[imageView.layer setCornerRadius:(hellowkitt.frame.size.width-th*2)/2];
[hellowkitt addSubview:imageView];
UIImageView * imageView1 = [UIImageView new];
[imageView1 setImage:[UIImage imageNamed:#"df"]];
[imageView1 setFrame:CGRectMake(hellowkitt.frame.origin.x+th, hellowkitt.frame.origin.y+th, hellowkitt.frame.size.width-th*2, hellowkitt.frame.size.height-th*2)];
[imageView1 setClipsToBounds:true];
[imageView1.layer setCornerRadius:imageView1.frame.size.height/2];
[self.view addSubview:imageView1];
int radius = imageView.frame.size.width/2+7;
arc = [CAShapeLayer layer];
[arc setFrame:imageView.frame];
UIBezierPath * aa =[UIBezierPath bezierPathWithArcCenter:CGPointMake(imageView.center.x, imageView.center.y) radius:radius startAngle:DEGREES_TO_RADIANS(0) endAngle:DEGREES_TO_RADIANS(360) clockwise:YES];
arc.path = aa.CGPath;
arc.bounds = CGPathGetBoundingBox(aa.CGPath);
arc.frame = arc.bounds;
arc.fillColor = [UIColor clearColor].CGColor;
arc.strokeColor = [UIColor purpleColor].CGColor;
arc.lineWidth = 40;
[arc setLineCap:#"round"];
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = CGRectMake(0, 0, hellowkitt.frame.size.width, hellowkitt.frame.size.height);
gradientLayer.colors = #[(__bridge id)[UIColor paperColorRedA700].CGColor, (__bridge id)[UIColor paperColorBlueA700].CGColor];
gradientLayer.backgroundColor = (__bridge CGColorRef)((__bridge id)[UIColor blackBean].CGColor);
gradientLayer.startPoint = CGPointMake(0.0,0.5);
gradientLayer.endPoint = CGPointMake(1.0,0.51);
CABasicAnimation *drawAnimation11 = [CABasicAnimation animationWithKeyPath:#"colors"];
drawAnimation11.duration = 2.00;
drawAnimation11.repeatCount = HUGE_VAL;
drawAnimation11.removedOnCompletion = NO;
drawAnimation11.fillMode = kCAFillModeForwards;
drawAnimation11.autoreverses = true;
drawAnimation11.fromValue = #[(__bridge id)[UIColor paperColorRedA700].CGColor, (__bridge id)[UIColor paperColorBlueA700].CGColor];
drawAnimation11.toValue = #[(__bridge id)[UIColor redColor].CGColor, (__bridge id)[UIColor blueColor].CGColor];
drawAnimation11.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
animation.byValue = #(4 * M_PI);
animation.duration = 1.5f;
animation.repeatCount = INFINITY;
animation.removedOnCompletion = NO;
[self this];
gradientLayer.mask = arc;
[hellowkitt.layer addSublayer:gradientLayer];
[hellowkitt.layer addAnimation:animation forKey:#"fasdfaasdf"];
[gradientLayer addAnimation:drawAnimation11 forKey:#"thatone"];
}

Get Raw Curve from CABasicAnimation

I am animation the stokeColor of a line drawn to a CAShapeLayer with a CABasicAnimation instance. I wish to use the curve of this animation to 'animate' a non-animatible property. I want to control the volume of a sample which takes a float value in tandem with the evolution of the stokeColor. So, my basic animation animates between two strokeColors with a duration of 0.2f. How can I 'animate' the volume of my sample with this information, so that when the animation occurs so does the change in volume?
This animates color, stroke, position, and rotation, full example:
Here's a fairly robust example, just use this as a template:
video showcase of example: https://www.youtube.com/watch?v=ucYqb0Gs1_8&feature=youtu.be
just call to the second method, and the second method will call the first method, and there you have it. Drop it into a view controllers view and you will see the magic happen, feel free to manipulate the code to your needs:
#define DEGREES_TO_RADIANS(degrees) ((M_PI * degrees)/ 180)
-(void)this {
[CATransaction begin];
[CATransaction setAnimationDuration:3.5];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
CABasicAnimation * drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.removedOnCompletion = YES;
drawAnimation.autoreverses = YES;
drawAnimation.repeatCount = INFINITY;
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:1.f];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[arc addAnimation:drawAnimation forKey:#"thisone"];
[CATransaction commit];
[CATransaction begin];
[CATransaction setAnimationDuration:3.5];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
CABasicAnimation *drawAnimation1 = [CABasicAnimation animationWithKeyPath:#"strokeStart"];
drawAnimation1.removedOnCompletion = YES;
drawAnimation1.autoreverses = YES;
drawAnimation1.repeatCount = INFINITY;
drawAnimation1.fromValue = [NSNumber numberWithFloat:0.0];
drawAnimation1.toValue = [NSNumber numberWithFloat:-1.0];
[arc addAnimation:drawAnimation1 forKey:#"myKey"];
[CATransaction commit];
}
-(void)doGradientoutline {
float th = 50.00;
UIView * hellowkitt = [UIView new];
[hellowkitt setFrame:CGRectMake(SCREEN_WIDTH/2-(30+th)*2/2-th, SCREEN_HEIGHT/4-th, (30+th)*2+th*2, (30+th)*2+th*2)];
[self.view addSubview: hellowkitt];
UIView * imageView = [UIView new];
[imageView setFrame:CGRectMake(th, th, hellowkitt.frame.size.width-th*2, hellowkitt.frame.size.height-th*2)];
[imageView setClipsToBounds:true];
[imageView.layer setCornerRadius:(hellowkitt.frame.size.width-th*2)/2];
[hellowkitt addSubview:imageView];
UIImageView * imageView1 = [UIImageView new];
[imageView1 setImage:[UIImage imageNamed:#"df"]];
[imageView1 setFrame:CGRectMake(hellowkitt.frame.origin.x+th, hellowkitt.frame.origin.y+th, hellowkitt.frame.size.width-th*2, hellowkitt.frame.size.height-th*2)];
[imageView1 setClipsToBounds:true];
[imageView1.layer setCornerRadius:imageView1.frame.size.height/2];
[self.view addSubview:imageView1];
int radius = imageView.frame.size.width/2+7;
arc = [CAShapeLayer layer];
[arc setFrame:imageView.frame];
UIBezierPath * aa =[UIBezierPath bezierPathWithArcCenter:CGPointMake(imageView.center.x, imageView.center.y) radius:radius startAngle:DEGREES_TO_RADIANS(0) endAngle:DEGREES_TO_RADIANS(360) clockwise:YES];
arc.path = aa.CGPath;
arc.bounds = CGPathGetBoundingBox(aa.CGPath);
arc.frame = arc.bounds;
arc.fillColor = [UIColor clearColor].CGColor;
arc.strokeColor = [UIColor purpleColor].CGColor;
arc.lineWidth = 40;
[arc setLineCap:#"round"];
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = CGRectMake(0, 0, hellowkitt.frame.size.width, hellowkitt.frame.size.height);
gradientLayer.colors = #[(__bridge id)[UIColor paperColorRedA700].CGColor, (__bridge id)[UIColor paperColorBlueA700].CGColor];
gradientLayer.backgroundColor = (__bridge CGColorRef)((__bridge id)[UIColor blackBean].CGColor);
gradientLayer.startPoint = CGPointMake(0.0,0.5);
gradientLayer.endPoint = CGPointMake(1.0,0.51);
CABasicAnimation *drawAnimation11 = [CABasicAnimation animationWithKeyPath:#"colors"];
drawAnimation11.duration = 2.00;
drawAnimation11.repeatCount = HUGE_VAL;
drawAnimation11.removedOnCompletion = NO;
drawAnimation11.fillMode = kCAFillModeForwards;
drawAnimation11.autoreverses = true;
drawAnimation11.fromValue = #[(__bridge id)[UIColor paperColorRedA700].CGColor, (__bridge id)[UIColor paperColorBlueA700].CGColor];
drawAnimation11.toValue = #[(__bridge id)[UIColor redColor].CGColor, (__bridge id)[UIColor blueColor].CGColor];
drawAnimation11.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
animation.byValue = #(4 * M_PI);
animation.duration = 1.5f;
animation.repeatCount = INFINITY;
animation.removedOnCompletion = NO;
[self this];
gradientLayer.mask = arc;
[hellowkitt.layer addSublayer:gradientLayer];
[hellowkitt.layer addAnimation:animation forKey:#"fasdfaasdf"];
[gradientLayer addAnimation:drawAnimation11 forKey:#"thatone"];
}
OH yeah, and one more thing, apple talked about some of this in 2014, they suggested using stroke to animate volume of sorts:
http://asciiwwdc.com/2014/sessions/419

Re-draw UIBezierPath based on random CGFloat value

I am developing a custom circular progress bar using UIBezierPath. I want to change the progress based on randomly generated CGFloat values.
My progress bar looks like this:
I drew the progress bar using the following code:
// Draw the arc with bezier path
int radius = 100;
CAShapeLayer *arc = [CAShapeLayer layer];
arc.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 50) radius:radius startAngle:M_PI endAngle:M_PI/150 clockwise:YES].CGPath;
arc.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);
arc.cornerRadius = 100.0;
arc.fillColor = [UIColor clearColor].CGColor;
arc.strokeColor = [UIColor purpleColor].CGColor;
arc.lineWidth = 6;
[self.view.layer addSublayer:arc];
// Animation of the progress bar
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 5.0; // "animate over 10 seconds or so.."
drawAnimation.repeatCount = 1.0; // Animate only once..
drawAnimation.removedOnCompletion = YES; // Remain stroked after the animation..
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:10.0f];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[arc addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
// Gradient of progress bar
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = self.view.frame;
gradientLayer.colors = #[(__bridge id)[UIColor redColor].CGColor,(__bridge id)[UIColor yellowColor].CGColor,(__bridge id)[UIColor purpleColor].CGColor ];
gradientLayer.startPoint = CGPointMake(0,0.1);
gradientLayer.endPoint = CGPointMake(0.5,0.2);
[self.view.layer addSublayer:gradientLayer];
gradientLayer.mask = arc;
refreshButton = [[UIButton alloc] initWithFrame:CGRectMake(50, 370, 50, 50)];
[refreshButton addTarget:self action:#selector(refresh:) forControlEvents:UIControlEventTouchUpInside];
[refreshButton setBackgroundImage:[UIImage imageNamed:#"refresh.png"] forState:UIControlStateNormal];
[self.view addSubview:refreshButton];
The UIButton with the green arrow generates randomly CGFloat values. So my question is how to reset the animation so that it stops at the corresponding position if it is between 0.0 and 1.0? I tried to insert the code for the animation inside the method of the button, but as a result the new arc was drawn on top of the existing one.
-(void)refresh:(id)sender{
NSLog(#"Refresh action:");
CGFloat randomValue = ( arc4random() % 256 / 256.0 );
NSLog(#"%f", randomValue);
valueLabel.text = #"";
valueLabel = [[UILabel alloc] initWithFrame:CGRectMake(150, 370, 100, 50)];
valueLabel.text = [NSString stringWithFormat: #"%.6f", randomValue];
[self.view addSubview:valueLabel];
// Draw the arc with bezier path
int radius = 100;
CAShapeLayer *arc = [CAShapeLayer layer];
arc.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 50) radius:radius startAngle:M_PI endAngle:M_PI/150 clockwise:YES].CGPath;
arc.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);
arc.cornerRadius = 100.0;
arc.fillColor = [UIColor clearColor].CGColor;
arc.strokeColor = [UIColor purpleColor].CGColor;
arc.lineWidth = 6;
[self.view.layer addSublayer:arc];
// Animation of the progress bar
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 5.0; // "animate over 10 seconds or so.."
drawAnimation.repeatCount = 1.0; // Animate only once..
drawAnimation.removedOnCompletion = YES; // Remain stroked after the animation..
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:randomValue];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[arc addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
}
I have no clue how to re-draw the arc, so any help or idea would be greatly appreciated!
Thanks a lot.
Granit
Try re-using the sublayer or replacing it. Make arc a class variable. Then cycle through the sublayers, remove the old arc then remake it with your new random float percentage. Once arc is a class variable then you can either replace it and animate from zero or you can animate from it's current percent to a new percent.

CABasicAnimation originates from top right rather than center

I have a circle that I animate growing outwards and getting bigger. Unfortunately despite me setting the anchorPoint it doesnt animate from the center outwards, it seems to lock position top left. What is going on?
See this image that depicts the circles off center
Code
- (void)setLayerProperties {
rippleLayer = [[CAShapeLayer alloc] init];
rippleLayer.fillColor = [UIColor clearColor].CGColor;
rippleLayer.strokeColor = _Color.CGColor;
//rippleLayer.contentsGravity = #"center";
rippleLayer.anchorPoint = CGPointMake(0.5,0.5);
rippleLayer.bounds = self.bounds;
rippleLayer.path = bezierPath.CGPath;
[self.layer addSublayer:rippleLayer];
}
// Now animate the circle outwards
rippleLayer.opacity = 1;
CABasicAnimation *scale = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
[scale setFromValue:[NSNumber numberWithFloat:1.0f]];
[scale setToValue:[NSNumber numberWithFloat:2.0f]];
[scale setRepeatCount:1];
[scale setDuration:1.0f];
//[scale setRemovedOnCompletion:YES];
[scale setFillMode:kCAFillModeForwards];
[rippleLayer addAnimation:scale forKey:scale.keyPath];
To scale layer at it's center, you need to set layer's position.This may help you
I want to draw a Smooth animations in iOS using CAPropertyAnimations
define RADIANS(angle) ((angle) / 180.0 * M_PI)
CGPoint testCenter = CGPointMake(144.5, 230.0);
CAShapeLayer *aTestLayer = [CAShapeLayer layer];
aTestLayer.path = [UIBezierPath bezierPathWithArcCenter:**CGPointZero** radius:15.0 startAngle:RADIANS(0) endAngle:RADIANS(360) clockwise:YES].CGPath;
aTestLayer.fillColor = [UIColor clearColor].CGColor;
aTestLayer.strokeColor = [UIColor whiteColor].CGColor;
**aTestLayer.position = testCenter;**
aTestLayer.borderWidth = 2.0;
[self.view.layer addSublayer:aTestLayer];
CABasicAnimation *pulse = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
pulse.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
pulse.duration = 2;
pulse.repeatCount = HUGE_VALF;
pulse.autoreverses = YES;
pulse.fromValue = [NSNumber numberWithFloat:1.0];
pulse.toValue = [NSNumber numberWithFloat:2.0];
[aTestLayer addAnimation:pulse forKey:nil];

Revealing a UIImage with a radial animation

I have a UIImageView that i want to slowly reveal in a radial clockwise fashion.
Here is an example of how i want to reveal the image over the course of 1 second. Im basically trying to simulate a marker drawing a circle around something.
Here's another variation of the animating a UIBezierPath. Bottom line, come up with a UIBezierPath and then just animate the drawing of it with a CABasicAnimation of the strokeEnd:
- (void)drawBezier
{
CGPoint startPoint = CGPointMake(self.view.frame.size.width / 2.0, 50);
UIBezierPath *path = [self strokePath:startPoint];
CAShapeLayer *oval = [[CAShapeLayer alloc] init];
oval.path = path.CGPath;
oval.strokeColor = [UIColor redColor].CGColor;
oval.fillColor = [UIColor clearColor].CGColor;
oval.lineWidth = 5.0;
oval.strokeStart = 0.0;
oval.strokeEnd = 1.0;
[self.view.layer addSublayer:oval];
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
anim.duration = 1.0;
anim.fromValue = [NSNumber numberWithFloat:0.0f];
anim.toValue = [NSNumber numberWithFloat:1.0f];
[oval addAnimation:anim forKey:#"strokeEndAnimation"];
}
You obviously have to create your bezier. This is my complicated little algorithm (it's an array of five points, the angle at each point, and the weight of the anchor points for each), but I'm sure you can come up with something simpler:
- (UIBezierPath *)strokePath:(CGPoint)startPoint
{
NSArray *points = [NSArray arrayWithObjects:
[NSValue valueWithCGPoint:CGPointMake(startPoint.x, startPoint.y)],
[NSValue valueWithCGPoint:CGPointMake(startPoint.x + 100.0, startPoint.y + 70.0)],
[NSValue valueWithCGPoint:CGPointMake(startPoint.x, startPoint.y + 140.0)],
[NSValue valueWithCGPoint:CGPointMake(startPoint.x - 100.0, startPoint.y + 70.0)],
[NSValue valueWithCGPoint:CGPointMake(startPoint.x + 10.0, startPoint.y + 10)],
nil];
NSArray *angles = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:M_PI_2 * 0.05],
[NSNumber numberWithFloat:M_PI_2],
[NSNumber numberWithFloat:M_PI],
[NSNumber numberWithFloat:M_PI_2 * 3],
[NSNumber numberWithFloat:0],
nil];
NSArray *weight = [NSArray arrayWithObjects:
[NSNumber numberWithFloat:100.0 / 1.7],
[NSNumber numberWithFloat: 70.0 / 1.7],
[NSNumber numberWithFloat:100.0 / 1.7],
[NSNumber numberWithFloat: 70.0 / 1.7],
[NSNumber numberWithFloat:100.0 / 1.7],
nil];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:startPoint];
for (NSInteger i = 1; i < 5; i++)
{
CGPoint pointPrevious = [[points objectAtIndex:(i-1)] CGPointValue];
CGPoint pointCurrent = [[points objectAtIndex:i] CGPointValue];
CGFloat anglePrevious = [[angles objectAtIndex:(i-1)] floatValue];
CGFloat angleCurrent = [[angles objectAtIndex:i] floatValue];
CGFloat weightPrevious = [[weight objectAtIndex:(i-1)] floatValue];
CGFloat weightCurrent = [[weight objectAtIndex:i] floatValue];
[path addCurveToPoint:pointCurrent
controlPoint1:CGPointMake(pointPrevious.x + cosf(anglePrevious) * weightPrevious,
pointPrevious.y + sinf(anglePrevious) * weightPrevious)
controlPoint2:CGPointMake(pointCurrent.x - cosf(angleCurrent) * weightCurrent,
pointCurrent.y - sinf(angleCurrent) * weightCurrent)];
}
return path;
}
And if this isn't acceptable (because it's a solid path rather than your artistic rendering) here's a trick that works (but is a little complicated, so bear with me). You can put your image on the layer, put the UIBezierPath oval on top of it and rather than animate the drawing of the bezier path in red, draw it in white so it obscures your image and blends into the background and then reverse the animation of the drawing of the UIBezierPath. That way, it starts with your image with a white UIBezierPath drawn on top of it, completely obscuring your image, and it animates the un-drawing of the UIBezierPath, revealing your image underneath. But anyway, this is how you add the image to a layer and how you "undraw" the bezier.
CAShapeLayer *imagelayer = [[CAShapeLayer alloc] init];
UIImage *image = [UIImage imageNamed:#"oval.png"];
imagelayer.contents = (id)image.CGImage;
imagelayer.frame = CGRectMake(startPoint.x - image.size.width / 2.0, startPoint.y, image.size.width, image.size.height);
[self.view.layer addSublayer:imagelayer];
CAShapeLayer *oval = [[CAShapeLayer alloc] init];
oval.path = path.CGPath;
oval.strokeColor = [UIColor whiteColor].CGColor;
oval.fillColor = [UIColor clearColor].CGColor;
oval.lineWidth = 80.0;
oval.strokeStart = 0.0;
oval.strokeEnd = 0.0;
[self.view.layer addSublayer:oval];
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
anim.duration = 1.0;
anim.fromValue = [NSNumber numberWithFloat:1.0f];
anim.toValue = [NSNumber numberWithFloat:0.0f];
[oval addAnimation:anim forKey:#"strokeEndAnimation"];
Clearly, some effort will have to be put into coming up with a UIBezierPath that directly covers up your image, etc., but it can be done. (That exercise, by the way, would be greatly simplified if you (a) drew the UIBezier, (b) take a screen snapshot, (c) throw it into photoshop to lend it the appearance you want, and then (d) use that for the underlying image.) Anyway, for my crude attempt below, I just made the thickness of the bezier incredibly thick, so I could be pretty sloppy about it, but you will want to be more careful (because I assume you're drawing your circle around something and you can't have the UIBezierPath overlay obscuring that).
Alternatively, if you want to use CodaFi's first suggestion, but don't have the individual images created yet, you can stick with this super-thick bezier path, but you could use a permutation of the code above to create your images (e.g. start with strokeStart at zero, don't both animating, but rather take a screen snapshot, do it again and again, incrementing the strokeStart by 1/30th (or whatever) each time, and taking another screen snapshot, etc.).
Hopefully someone can come up with a more elegant solution!

Resources