如何获得 CAShapeLayer superview 位置?

How to get CAShapeLayer superview position?

我正在制作圆形图表,我需要在突出显示的圆圈(不是阴影)的末端添加小视图以指向目标值。能否根据笔划终点找到圆(高亮)超级视图x,y位置?

UIView->Circle(使用 CAShapeLayerUIBezierPath)。我需要根据结束 Circle 笔划值获得 UIView 的位置。

请参考这个link(比如虚线的23%)http://support.softwarefx.com/media/74456678-5a6a-e211-84a5-0019b9e6b500/large

提前致谢! Need to find green end circle position

更新: 我已经尝试过 alexburtnik 代码,实际上是在 objective c 中处理顺时针图形,但这在这里不是问题。我像 alexburtnik 提到的那样尝试过,我相信它非常适合逆时针图表。我们需要对代码进行一些更改以使其也适用于 Clockwise,如果您知道,请提供解决方案。

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

我尝试了上面提到的 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];} 

要理解这一点,您需要做一些数学运算。

当您在 UIBezierPath 中定义弧线时,您会传入 startAngleendAngle。在这种情况下,我相信您想在笔划的末端对齐另一个视图。

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

现在您需要计算 endAngle 参数的正弦和余弦。结束角度的正弦将为您提供单位坐标中的 y 位置,也就是范围 [-1, 1],结束角度的余弦也会为您提供单位坐标中的 x 位置。然后可以根据弧的大小缩放这些坐标并进行调整以匹配视图,然后移动视图的位置以获得笔划结束的位置。

这是此效果的示例操场。我对齐了一个 UILabel,因此文本在结束点上居中。将其粘贴到 iOS playground 中以自己尝试。

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

1.数学

让我们暂时忘掉iOS,来解决一道数学题。

问题: 找到给定 α 的 (x;y) 点。

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

2。替换

你的起点不是三角函数的 0,而是 π/2。你的弧角也是由进度参数定义的,所以它给你带来了这个:

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

3。现在让我们 return 到 iOS:

由于在 iOS 中原点位于左上角,y 轴已反转,因此您应该这样转换之前的解决方案:

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

绿色为数学坐标,蓝色为iOS坐标系,我们转化为:

现在您需要做的就是将结果乘以宽度和高度:

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

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)
}

编辑

如果要顺时针旋转,只需将角度乘以-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)
}

希望这对您有所帮助。