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

时间:2016-11-15 19:46:33

标签: ios objective-c calayer uibezierpath cashapelayer

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

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

请参阅此链接(如带有虚线的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];}

Please check my orignal graph

我试着提到了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];} 

Please check this result

2 个答案:

答案 0 :(得分:5)

<强> 1。数学

让我们暂时忘掉iOS并解决数学问题。

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

解决方案: x = cos(α); y = sin(α)

math

<强> 2。取代

你的起点在三角学方面不是0,而是在π/ 2 。您的弧角也是由进度参数定义的,因此它会引导您:

  

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

第3。现在让我们回到iOS:

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

  

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

绿色代表数学坐标,蓝色代表iOS坐标系,我们将其转换为:

iOS

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

  

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

     

x'= 0.5 *宽*(1 + sin(进展* 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)
}

希望这有帮助。

答案 1 :(得分:2)

为了明白这一点,你需要做一些数学运算。

在UIBezierPath中定义弧时,可以传入startAngleendAngle。在这种情况下,我相信你想在中风结束时排列另一个视图。

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

现在您需要获取endAngle参数的正弦和余弦。结束角度的正弦将给出单位坐标中的y位置,也就是在[-1,1]范围内,并且结束角度的余弦将在单位坐标中给出x位置。然后可以通过弧的大小来缩放这些坐标,并进行调整以使其与视图匹配,然后移动到视图的位置以获得笔划结束的位置。

以下是此效果的示例操场。我对齐了UILabel,因此文本在终点上居中。将其粘贴到iOS游乐场,自行试用。

Demonstrating the centered view

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