Rotating CAShapeLayer moves the position too

2020-03-11 04:39发布

问题:

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

回答1:

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.

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

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:

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.

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



回答2:

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.