【问题标题】:How to get CAShapeLayer superview position?如何获得 CAShapeLayer 超级视图位置?
【发布时间】:2017-03-29 20:13:16
【问题描述】:

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

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

请参考此链接(如 23% 虚线)http://support.softwarefx.com/media/74456678-5a6a-e211-84a5-0019b9e6b500/large

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

更新: 我已经尝试过 alexburtnik 代码,实际上正在处理目标 c 中的顺时针图,但这在这里不是问题。我尝试过 alexburtnik 提到的,我相信它非常适用于反时钟明智的图表。我们需要对代码进行一些更改以适用于顺时针,如果您知道请给出解决方案。

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

【问题讨论】:

  • convertPoint:toView: 应该可以解决问题

标签: ios objective-c calayer uibezierpath cashapelayer


【解决方案1】:

1.数学

让我们暂时忘记 iOS 并解决一个数学问题。

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

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

2。换人

就三角学而言,您的起点不是 0,而是 π/2。此外,您的弧角由进度参数定义,因此它会将您带到:

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

3.现在让我们回到 iOS:

由于在 iOS 中原点位于左上角,因此 y 轴被还原,您应该以这种方式转换以前的解决方案:

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

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

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

x' = 0.5 * 宽度 * (1 - sin(progress * 2 * π))
y' = 0.5 * 高度 * (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 * 宽度 * (1 + sin(进度 * 2 * π))
y' = 0.5 * 高度 * (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)
}

希望这会有所帮助。

【讨论】:

  • 感谢您的回复!实际上,我正在研究目标 c 中的顺时针图,但这在这里不是问题。正如你提到的,我试过了,我相信它非常适用于逆时针图形。我们也需要对代码进行一些更改以适用于顺时针,如果您知道请给出解决方案。我在上面的原始问题中添加了我的代码和屏幕截图。
  • 那你的问题有误导性,因为截图看起来不像是顺时针的进度条。
  • 是的,这是我的错误。对不起!你也能有一个顺时针的解决方案吗?
  • @user1591698 尝试更新的代码。我还没有检查过,但应该可以工作
  • 太棒了!快乐编码 ;)
【解决方案2】:

要明白这一点,您需要做一些数学运算。

在 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

【讨论】:

  • 感谢您的帮助。我在上面添加了我的结果。请检查一下。
  • @user1591698 看起来你已经让它工作了,或者至少非常接近。您可能需要为您的用例删除 endY += height
  • 是的!再次感谢:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多