質問

I'm drawing a simple circle in the center of the screen:

int radius = 100;

- (void)addCircle {
    self.circle = [CAShapeLayer layer];
    self.circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)
                                             cornerRadius:radius].CGPath;
    self.circle.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
                                  CGRectGetMidY(self.view.frame)-radius);

    self.circle.fillColor = [UIColor clearColor].CGColor;
    self.circle.strokeColor = [UIColor blackColor].CGColor;
    self.circle.lineWidth = 5;

    [self.view.layer addSublayer:self.circle];
}

Using the pinch gesture, I allow the user to increase/decrease the radius of the shape:

- (void)scale:(UIPinchGestureRecognizer *)gestureRecognizer {    
    if (gestureRecognizer.state != UIGestureRecognizerStateBegan) {
        if (gestureRecognizer.scale < lastScale) {
            --radius;
        }
        else if (gestureRecognizer.scale > lastScale) {
            ++radius;
        }
        // Center the shape in self.view
        self.circle.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius, CGRectGetMidY(self.view.frame)-radius);

        self.circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius) cornerRadius:radius].CGPath;
    }

    lastScale = gestureRecognizer.scale;
}

However, the circle doesn't stay dead center. Instead, it bounces around the middle and doesn't settle until the gesture finishes.

Does anyone know why this is happening and if so, how I can prevent it?

役に立ちましたか?

解決

There are a few problems in your code. As @tc. said, you're not setting the shape layer's frame (or bounds). The default layer size is CGSizeZero, which is why you're having to offset the layer's position by the radius every time you change the radius.

Also, the position and path properties of a shape layer are animatable. So by default, when you change them, Core Animation will animate them to their new values. The path animation is contributing to your unwanted behavior.

Also, you should set the layer's position or frame based on self.view.bounds, not self.view.frame, because the layer's position/frame is the coordinate system of self.view, not the coordinate system of self.view.superview. This will matter if self.view is the top-level view and you support interface autorotation.

I would suggest revising how you're implementing this. Make radius a CGFloat property, and make setting the property update the layer's bounds and path:

@interface ViewController ()

@property (nonatomic, strong) CAShapeLayer *circle;
@property (nonatomic) CGFloat radius;

@end

@implementation ViewController

- (void)setRadius:(CGFloat)radius {
    _radius = radius;
    self.circle.bounds = CGRectMake(0, 0, 2 * radius, 2 * radius);
    self.circle.path = [UIBezierPath bezierPathWithOvalInRect:self.circle.bounds].CGPath;
}

If you really want to force the radius to be an integer, I suggest internally tracking it as a float anyway, because the user interaction is smoother if it's a float. Just round it in a temporary variable before creating the CGRect for the bounds and path:

    CGFloat intRadius = roundf(radius);
    self.circle.bounds = CGRectMake(0, 0, 2 * intRadius, 2 * intRadius);
    self.circle.path = [UIBezierPath bezierPathWithOvalInRect:self.circle.bounds].CGPath;

In addCircle, just set the radius property and let that setter take care of setting the layer's bounds and path. Also defer setting the layer's position until the system's layout phase. That way, you'll reposition the circle in the center again after an interface rotation.

- (void)viewDidLoad {
    [super viewDidLoad];
    [self addCircle];
}

- (void)addCircle {
    self.circle = [CAShapeLayer layer];
    self.circle.fillColor = nil;
    self.circle.strokeColor = [UIColor blackColor].CGColor;
    self.circle.lineWidth = 5;
    self.radius = 100;
    [self.view.layer addSublayer:self.circle];
    [self.view setNeedsLayout];
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    self.circle.position = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds));
}

Finally, to handle a pinch gesture, just set the new radius to the old radius times the gesture's scale. The radius setter will take care of updating the layer's path and bounds. Then reset the gesture's scale to 1. This is simpler than tracking the gesture's prior scale. Also, use CATransaction to disable animation of the path property.

- (IBAction)pinchGestureWasRecognized:(UIPinchGestureRecognizer *)recognizer {
    [CATransaction begin]; {
        [CATransaction setDisableActions:YES];
        self.radius *= recognizer.scale;
        recognizer.scale = 1;
    } [CATransaction commit];
}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top