简体   繁体   English

如何获得CAShapeLayer的超级视图位置?

[英]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? 我可以根据笔画终点找到一个圆(高亮)超视图x,y位置吗?

UIView->Circle (using CAShapeLayer and UIBezierPath ). UIView->Circle (使用CAShapeLayerUIBezierPath )。 I need to get a position of UIView based on ending Circle stroke value. 我需要根据结束Circle笔划值获得UIView的位置。

Please refer this link(like 23% with dotted line) http://support.softwarefx.com/media/74456678-5a6a-e211-84a5-0019b9e6b500/large 请参考此链接(如带有虚线的23%) 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. 更新:我已经尝试了alexburtnik代码,实际上我正在研究目标c中的时钟图,但这不是问题。 I tried as alexburtnik mentioned, I believe it perfectly works for anti-clock wise graph. 我试着像alexburtnik提到的那样,我相信它完全适用于反时钟图。 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];}

请检查我的原始图表

And i tried as esthepiking mentioned, here i added code and screenshot 我试着提到了esthepiking,在这里我添加了代码和截图

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

请检查此结果

1. Math 数学

Let's forget about iOS for a moment and solve a math problem. 让我们暂时忘掉iOS并解决数学问题。

Problem: find (x;y) point for a given α. 问题:找到给定α的(x; y)点。

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

数学

2. Substitution 2.替代

Your starting point is not at 0 in terms of trigonometry, but rather at π/2 . 你的起点在三角学方面不是0,而是在π/ 2 Also your arc angle is defined by progress parameter, so it brings you to this: 你的弧角也是由进度参数定义的,所以它带你到这个:

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

3. Now let's return to iOS: 3.现在让我们回到iOS:

Since in iOS origin is in the top left corner an y axis is reverted you should convert the previous solution this way: 由于在iOS原点位于左上角,y轴被还原,您应该以这种方式转换以前的解决方案:

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

Green is for mathematical coordinates, blue is for iOS coordinate system, which we transformed to: 绿色用于数学坐标,蓝色用于iOS坐标系,我们将其转换为:

iOS版

Now everything you need to do is to multiply the result by width and height: 现在你需要做的就是将结果乘以宽度和高度:

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

4. Code: 4.代码:

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 : 如果要将其切换为顺时针方向,只需将角度乘以-1

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

x' = 0.5 * width * (1 + sin(progress * 2 * π)) x'= 0.5 *宽*(1 + sin(进度* 2 *π))
y' = 0.5 * height * (1 - cos(progress * 2 * π)) y'= 0.5 *高度*(1 - cos(进度* 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. 希望这可以帮助。

To get the point, you'll need to do a little bit of math. 为了明白这一点,你需要做一些数学运算。

When you define an arc in a UIBezierPath, you pass in the startAngle and the endAngle . 在UIBezierPath中定义弧时,传入startAngleendAngle In this case, I believe you want to line up another view with the end of the stroke. 在这种情况下,我相信你想要在笔画结束时排列另一个视图。

UIBezierPath(arcCenter center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool)

Now you'll need to take the sine and cosine of that endAngle parameter. 现在你需要获取endAngle参数的正弦和余弦。 Sine of the end angle will give you the y position in unit coordinates, aka on the range [-1, 1], and the cosine of the end angle will give you the x position also in unit coordinates. 结束角度的正弦将给出单位坐标中的y位置,也就是在[-1,1]范围内,并且结束角度的余弦将在单位坐标中给出x位置。 Those coordinates can then be scaled by the size of the arc and adjusted so it matches the view, then shifted by the view's position to get the position of the end of the stroke. 然后可以通过弧的大小来缩放这些坐标,并进行调整以使其与视图匹配,然后移动视图的位置以获得笔划结束的位置。

Here's an example playground for this effect. 这是这种效果的示例操场。 I aligned a UILabel so the text is centered over the end point. 我对齐了UILabel,因此文本在终点上居中。 Paste this into an iOS playground to try it out yourself. 将其粘贴到iOS游乐场,自行尝试。

展示中心视图

import UIKit
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

// Frame to wrap all of this inside of
let frame = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
frame.backgroundColor = .white

let radius = 100 as CGFloat

// Outermost gray circle
let grayCircle = CAShapeLayer()
grayCircle.frame = frame.frame
grayCircle.path = UIBezierPath(arcCenter: frame.center, radius: radius, startAngle: 0, endAngle: CGFloat(M_PI * 2), clockwise: true).cgPath
grayCircle.strokeColor = UIColor.lightGray.cgColor
grayCircle.lineWidth = 10
grayCircle.fillColor = UIColor.clear.cgColor

frame.layer.addSublayer(grayCircle)

// Ending angle for the arc
let endAngle = CGFloat(M_PI / 5)

// Inner green arc
let greenCircle = CAShapeLayer()
greenCircle.frame = frame.frame
greenCircle.path = UIBezierPath(arcCenter: frame.center, radius: radius, startAngle: CGFloat(-M_PI_2), endAngle: endAngle, clockwise: false).cgPath
greenCircle.strokeColor = UIColor.green.cgColor
greenCircle.lineWidth = 10
greenCircle.fillColor = UIColor.clear.cgColor
greenCircle.lineCap = kCALineCapRound

frame.layer.addSublayer(greenCircle)

// Size for the text
let width = 75 as CGFloat
let height = 30 as CGFloat

// 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
var endX = (cos(endAngle) / 2 + 0.5)
var endY = (sin(endAngle) / 2 + 0.5)

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

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

// Vertically align the label
endY += height

// Setup the label
let text = UILabel(frame: CGRect(x: endX, y: endY, width: width, height: height))
text.text = "Stroke end!"
text.font = UIFont.systemFont(ofSize: 14)
frame.addSubview(text)

PlaygroundPage.current.liveView = frame

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM