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.
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!