如何获得 CAShapeLayer superview 位置?
How to get CAShapeLayer superview position?
我正在制作圆形图表,我需要在突出显示的圆圈(不是阴影)的末端添加小视图以指向目标值。能否根据笔划终点找到圆(高亮)超级视图x,y位置?
UIView->Circle
(使用 CAShapeLayer
和 UIBezierPath
)。我需要根据结束 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 中定义弧线时,您会传入 startAngle
和 endAngle
。在这种情况下,我相信您想在笔划的末端对齐另一个视图。
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)
}
希望这对您有所帮助。
我正在制作圆形图表,我需要在突出显示的圆圈(不是阴影)的末端添加小视图以指向目标值。能否根据笔划终点找到圆(高亮)超级视图x,y位置?
UIView->Circle
(使用 CAShapeLayer
和 UIBezierPath
)。我需要根据结束 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 中定义弧线时,您会传入 startAngle
和 endAngle
。在这种情况下,我相信您想在笔划的末端对齐另一个视图。
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)
}
希望这对您有所帮助。