Search code examples
iosobjective-cuibezierpathcashapelayer

Drop `points` along a UIBezierPath such that `center` of points lies ON the path


The problem statement:

Create a semi-circular dial that has dots along it's circumference. User can pan and rotate this dial.

enter image description here

Approach:

  • Created a UIView
  • Added a circle with radius that's double the height of the UIView (using [UIBezierPath bezierPathWithOvalInRect] and CAShapeLayer)
  • Add dots (circular CALayer objects) along the circumference of this circle, such that the center of these dots lies ON the circumference.

The obstacle:

The dots are plotted along a path, but the center of these dots does not lie on the circumference.

enter image description here

The code is as follows:

@property (nonatomic, retain) CAShapeLayer* dialLineLayer;

- (instancetype)initWithCoder:(NSCoder *)aDecoder {

    if (self = [super initWithCoder:aDecoder]) {

        self.dialLineLayer = [CAShapeLayer layer];
        CGRect path = CGRectMake(SCREEN_WIDTH /2 - self.radius, -self.radius, 2 * self.radius, 2 * self.radius);

        [self.dialLineLayer setPath:[[UIBezierPath bezierPathWithOvalInRect:path] CGPath]];
        [self.dialLineLayer setStrokeColor:[[UIColor la_white10Color] CGColor]];
        [self.dialLineLayer setFillColor:[[UIColor clearColor] CGColor]];
        [self.dialLineLayer setLineWidth:3.0f];
        [self.layer addSublayer:self.dialLineLayer];

        [self addDotsOnLineLayer];
    }

    return self;
}

- (CGFloat) radius {

    // View has an aspect ratio constraint of 75:34 (W:H)

    return SCREEN_WIDTH * 34 / 75 - 10; // Circle radius is 10px less that View's height
}

- (CGFloat) circumference {

    return M_2_PI * self.radius; // 2Pi * radius
}

- (void) addDotsOnLineLayer {

    CGPoint dotPoint = CGPointMake(SCREEN_WIDTH /2 - self.radius - self.radius/2, 0);

    for (double t = 0; t < 2*M_PI; t += 0.2) {

        CGFloat dotRadius = arc4random_uniform(10) - arc4random_uniform(3); // Random radius between 3px and 10px

        CALayer* dotLayer = [CALayer layer];
        dotLayer.frame = CGRectMake(dotPoint.x, dotPoint.y, dotRadius, dotRadius);
        dotLayer.cornerRadius = dotRadius / 2;
        dotLayer.masksToBounds = YES;
        dotLayer.backgroundColor = [UIColor greenColor].CGColor;

        dotPoint.x = self.radius * cos(t) + (SCREEN_WIDTH / 2);
        dotPoint.y = self.radius * sin(t) + 0;

        [self.dialLineLayer addSublayer:dotLayer];
    }
}

Solution

  • Try this: (Brace for major 'doh' + facepalm ;) )

    dotLayer.frame = CGRectMake(0, 0, dotRadius, dotRadius);
    dotLayer.position = dotPoint;
    

    Explanation

    You are currently positioning the origin of your layer on the path. (Remove the corner radius and you'll see what I mean.) However, since you want the center of the layer on the path, you have to assign to .position.

    Note that this is only true, as long as you leave the .anchorPoint property of the layer at {0.5, 0.5}. See Core Animation Basics for more info.