Reputation: 292
How to change direction and start position of CALayer
animation?
For example, if I animate a circle shape using following code:
-(void)drawCircle {
CAShapeLayer *circleShapeLayer = [[CAShapeLayer alloc] init];
circleShapeLayer.path = [self circleBezierPath].CGPath;
circleShapeLayer.strokeColor = [UIColor lightGrayColor].CGColor;
circleShapeLayer.fillColor = [UIColor clearColor].CGColor;
circleShapeLayer.lineWidth = 2;
[self.view.layer addSublayer:circleShapeLayer];
CABasicAnimation *circleShapeAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
circleShapeAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
circleShapeAnimation.duration = 1.0f;
circleShapeAnimation.fromValue = @(0.0f);
circleShapeAnimation.toValue = @(1.0f);
[circleShapeLayer addAnimation:circleShapeAnimation forKey:@"strokeEnd"];
}
-(UIBezierPath *)circleBezierPath {
UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 100, 100, 100)];
return circlePath;
}
Animation always starts at 3 o'clock and animates in a clockwise direction.
How do I force it to start at 12 o'clock (or any other position) and animate in a counterclockwise direction?
Upvotes: 1
Views: 1428
Reputation: 56625
When you create an oval in a rectangle (ether using UIBezierPath of the CGPath functions) it creates an oval or ellipse that fits the specified rectangle. This means that the center of the oval is not the same as the origin of the rectangle:
The animation always starts at 3 o'clock and goes counter clockwise because that is the arcing path that is a circle from 0 to 2π in the default coordinate system:
This means that the path you are animating the stroke of really looks like this:
Looking at the path, you could move the start point by rotating the view. This is not recommended because it has side effects. If there are any sublayers, those will be rotated as well. The stroke direction could be changed either by flipping the view (scaling negative along one axis) or by animating the start of the stroke from 1 to 0 instead of the end of the stroke.
All of theses solutions will work, but has side effects and is less clear that what is preferable.
I like to look at the problem like this: the animation code is doing the right thing, animating the stroke from nothing to all of it, but the start point and stroke direction of the path is not what's expected. This means that the "problem" lies in the path.
Instead of using bezierPathWithOvalInRect:
, you can create your own full circle using an arc that goes from any start angle to any end angle in either a clockwise or counter clockwise direction. All that's required is that you calculate the center of the circle (and the start and end angles). Looking at the default coordinate system, we can see that the start angle is 3π/2 or -π/2. The end angle is then plus or minus 2π from the start angle (depending on if it's clockwise or counter clockwise).
Creating an arc like this could look something like this:
- (UIBezierPath *)circleBezierPath
{
CGRect boundingRect = CGRectMake(100, 100, 100, 100);
CGPoint center = CGPointMake(CGRectGetMidX(boundingRect), CGRectGetMidY(boundingRect));
CGFloat radius = MIN(CGRectGetWidth(boundingRect), CGRectGetHeight(boundingRect));
CGFloat twelveOClockAngle = -M_PI_2;
BOOL clockwise = NO; // NO for counter clockwise
CGFloat startAngle = twelveOClockAngle;
CGFloat endAngle = startAngle + (2.0*M_PI * (clockwise ? 1.0 : -1.0));
return [UIBezierPath bezierPathWithArcCenter:center
radius:radius
startAngle:startAngle
endAngle:endAngle
clockwise:clockwise];
}
Upvotes: 11