user1591698 user1591698 - 20 days ago 5
iOS Question

How to get CAShapeLayer superview position?

I'm working on a circular chart, and I need to add small view at end of the highlighted circle (not shadow) for pointing at the target value. Can I find a circle (highlight) super view x,y positions based on the stroke end point?

UIView->Circle
(using
CAShapeLayer
and
UIBezierPath
). I need to get a position of
UIView
based on ending
Circle
stroke value.

Please refer this link(like 23% with dotted line) http://support.softwarefx.com/media/74456678-5a6a-e211-84a5-0019b9e6b500/large

Thanks in advance!
Need to find green end circle position

Update:
I have tried alexburtnik code, Actually am working on clock-wise graph in objective c but that's not a problem here. I tried as alexburtnik mentioned, I believe it perfectly works for anti-clock wise graph. We need to make some change in code to work for Clockwise too, Please give solution if you know.

CGFloat radiusCircle = (self.frame.size.height * 0.5) - ([_lineWidth floatValue]/2.0f);
-(void)addTargetViewWithOptions:(CGFloat)progress andRadius:(CGFloat)radius{

CGFloat x = radius * (1 + (cos(M_PI * (2 * progress + 0.5))));
CGFloat y = radius * (1 - (sin(M_PI * (2 * progress + 0.5))));
UIView *targetView = [[UIView alloc]initWithFrame:CGRectMake(x, y, 40, 30)];
targetView.backgroundColor = [UIColor greenColor];
[self addSubview:targetView];}


Please check my orignal graph

And i tried as esthepiking mentioned, here i added code and screenshot

-(void)addTargetView{

CGFloat endAngle = -90.01f;
radiusCircle = (self.frame.size.height * 0.5) - ([_lineWidth floatValue]/2.0f);
endAngleCircle = DEGREES_TO_RADIANS(endAngle);//-1.570971

// Size for the text
CGFloat width = 75;
CGFloat height = 30;

// Calculate the location of the end of the stroke
// Cos calculates the x position of the point (in unit coordinates)
// Sin calculates the y position of the point (in unit coordinates)
// Then scale this to be on the range [0, 1] to match the view
CGFloat endX = (cos(endAngleCircle) / 2 + 0.5);
CGFloat endY = (sin(endAngleCircle) / 2 + 0.5);

// Scale the coordinates to match the diameter of the circle
endX *= radiusCircle * 2;
endY *= radiusCircle * 2;

// Translate the coordinates based on the location of the frame
endX -= self.frame.origin.x;
endY -= self.frame.origin.y;

// Vertically align the label
endY += height;

// Setup the label

UIView *targetView = [[UIView alloc]initWithFrame:CGRectMake(endX, endY, width, height)];
targetView.backgroundColor = [UIColor redColor];
[self addSubview:targetView];}


Please check this result

Answer

1. Math

Let's forget about iOS for a moment and solve a math problem.

Problem: find (x;y) point for a given α.

Solution: x = cos(α); y = sin(α)

math

2. Substitution Your starting point is not at 0 in terms of trigonometry, but rather at π/2. Also your arc angle is defined by progress parameter, so it brings you to this:

x = cos(progress * 2 * π + π/2) = -sin(progress * 2 * π)
y = sin(progress * 2 * π + π/2) = cos(progress * 2 * π)

3. Now let's return to iOS:

Since in iOS origin is in the top left corner an y axis is reverted you should convert the previous solution this way:

x' = 0.5 * (1 + x)
y' = 0.5 * (1 - y)

Green is for mathematical coordinates, blue is for iOS coordinate system, which we transformed to:

iOS

Now everything you need to do is to multiply the result by width and height:

x' = 0.5 * width * (1 - sin(progress * 2 * π))
y' = 0.5 * height * (1 - cos(progress * 2 * π))

4. Code:

func point(progress: Double, radius: CGFloat) -> CGPoint {
    let x = radius * (1 - CGFloat(sin(progress * 2 * Double.pi))))
    let y = radius * (1 - CGFloat(cos(progress * 2 * Double.pi))))
    return CGPoint(x: x, y: y)
}

Edit

If you want to switch it to clockwise direction, just multiply angle by -1:

x = cos(-progress * 2 * π + π/2) = -sin(-progress * 2 * π) = sin(progress * 2 * π)
y = sin(-progress * 2 * π + π/2) = cos(-progress * 2 * π) = cos(progress * 2 * π)

x' = 0.5 * width * (1 + sin(progress * 2 * π))
y' = 0.5 * height * (1 - cos(progress * 2 * π))

func point(progress: Double, radius: CGFloat) -> CGPoint {
    let x = radius * (1 + CGFloat(sin(progress * 2 * Double.pi))))
    let y = radius * (1 - CGFloat(cos(progress * 2 * Double.pi))))
    return CGPoint(x: x, y: y)
}

Hope this helps.