Rotating CAShapeLayer moves the position too
Asked Answered
H

2

12

I am creating a CAShapeLayer. I am adding rotation animation to it. Somehow transformation result is weird. The child layer is moving along with rotation. I need it to be in fixed center/position(anchor) and rotate. I know its messing up geometric transformation but i am unable to get it right.

I have tried setting anchorpoint. I also followed this post

Here is the code:

UIBezierPath *circle = [UIBezierPath bezierPathWithArcCenter:CGPointMake(75, 125)
                                                      radius:50
                                                  startAngle:0
                                                    endAngle:1.8 * M_PI
                                                   clockwise:YES];

CAShapeLayer *circleLayer = [CAShapeLayer layer];
[circleLayer setFrame:CGRectMake(200 - 50, 300 - 50, 100, 100)];
circleLayer.path   = circle.CGPath;
circleLayer.strokeColor = [UIColor orangeColor].CGColor;
[circleLayer setFillColor:[UIColor clearColor].CGColor];
circleLayer.lineWidth   = 3.0;

if ([circleLayer animationForKey:@"SpinAnimation"] == nil) {
    CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    animation.fromValue = [NSNumber numberWithFloat:0.0f];
    animation.toValue = [NSNumber numberWithFloat: 2 * M_PI];
    animation.duration = 10.0f;
    animation.repeatCount = INFINITY;
    animation.removedOnCompletion = NO;
    [circleLayer addAnimation:animation forKey:@"SpinAnimation"];
}

[self.view.layer addSublayer:circleLayer];
Hotbox answered 24/7, 2015 at 9:58 Comment(0)
P
36

tl;dr: your shape layer is missing a frame (probably the bounding box of its path).


The confusion comes from the fact that the layer being rotated has no frame. This means that its size is 0⨉0. This combined with the fact that the shape layer allows the shape to be drawn outside of its bounds makes it look like the rotation is happening around an exterior point. But it's not.

The problem

Without any modification to the anchor point, the transform is happening relative to the center of the layer's own bounds. For a layer with no size, the center is the same as the origin ((x+width/2, y+height/2) is the same as (x, y) when both width and height are 0).

The local origin (relative to the layer's bounds) is also where the path is drawn relative to. The point (0, 0) for the path is in the origin of the shape layer.

If we were to display the center (and in this case the origin) of the layer, this is what it would look like.

enter image description here

This is also the point that the shape is rotated around.

enter image description here

Solving it...

The are essentially two ways of solving this problem, making it rotate around the center of the circle. One is to alter the path so that the circles center is at (0,0), the origin. Another solution, which I generally prefer, is to give the layer a size that matches the size of its path, the paths bounding box:

circleLayer.bounds = CGPathGetBoundingBox(circle.CGPath);

Here I'm showing the layer with a very light gray background color:

enter image description here

I sometimes find it useful to give a shape layer a background color during development as a debugging tool so that I can see that its frame matches my expectations.

Note that when you change the bounds of a layer or view, it resizes relative to its position. This means that the center point stays the same and the origin moves, meaning that the path will effectively move.

Now that the layer has a size, and the center of the bounds matches the center of the circle, it will look like the circle is rotating around it's center when you rotate the shape layer.

enter image description here

When you see that everything is working, you can remove the background color again.

Pontius answered 24/7, 2015 at 12:3 Comment(5)
David, thank you so much. In short DONT FORGET TO SET THE BOUNDS of your CAShapeLayer - ie, don't be like me :) Thank goodness for your great post.Dragon
For me it was that the frame was not set to my layer. So circleLayer.frame = CGPathGetBoundingBox(circle.CGPath); solved my problem.Pail
Thanks guys, in short, DONT FORGET TO SET THE BOUNDS AND FRAME of the CAShapeLayer!!!Pahl
@Pahl the bounds and frame properties are interconnected so you only need to set one of them to give the shape layer a size.Rochkind
@DavidRönnqvist I've just had this kind of problem similar to this, tried to set only bounds, then only frame, both not working. Then, set both of them and it works. I can't tell much about this ¯_(ツ)_/¯Pahl
L
1

It works right. Just enable frame for your layer:

circleLayer.borderWidth = 1;
circleLayer.borderColor = [UIColor redColor].CGColor;

And you see that frame rotating right. Also better to use byValue here:

CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
animation.byValue = @(2 * M_PI);
animation.duration = 10.0f;
animation.repeatCount = INFINITY;
animation.removedOnCompletion = NO;
[circleLayer addAnimation:animation forKey:@"SpinAnimation"];

So your problem is a geometry of shape.

Loess answered 24/7, 2015 at 10:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.