UIBezierPath带有圆边的三angular形

我devise了这个代码来生成一个Bezierpath,用作CAShapeLayer的path来掩盖UIView(视图的高度和宽度都是可变的)

这段代码会生成一个尖锐的三angular形,但是我想把它变成圆angular。 我花了好2个小时试图与addArcWithCenter...lineCapStylelineJoinStyle等工作,但似乎没有为我工作。

 UIBezierPath *bezierPath = [UIBezierPath bezierPath]; CGPoint center = CGPointMake(rect.size.width / 2, 0); CGPoint bottomLeft = CGPointMake(10, rect.size.height - 0); CGPoint bottomRight = CGPointMake(rect.size.width - 0, rect.size.height - 0); [bezierPath moveToPoint:center]; [bezierPath addLineToPoint:bottomLeft]; [bezierPath addLineToPoint:bottomRight]; [bezierPath closePath]; 

所以我的问题是,我将如何绕过UIBezierPath中的三angular形的所有边缘(我是否需要子层,多个path等)?

注意我没有绘制这个BezierPath,所以所有CGContext...函数drawRect不能帮助我:(

谢谢!

编辑

FWIW:这个答案通过解释CGPathAddArcToPoint(...)为你做什么来CGPathAddArcToPoint(...)教育目的。 我强烈build议您仔细阅读它,因为它可以帮助您理解并欣赏CGPath API。 那么你应该继续使用它,如在an0的答案中所看到的那样,而不是在应用程序中围绕边时使用此代码。 这个代码只能作为参考,如果你想玩弄和了解这样的几何计算。


原始答案

因为我发现这样的问题很有趣,我不得不回答:)

这是一个很长的答案。 没有简短的版本:D


注意:为了我自己的简单性,我的解决scheme正在对用于形成三angular形的点进行一些假设,例如:

  • 三angular形的面积足够大,以适应圆angular(例如,三angular形的高度大于圆angular的直径,我不检查或试图阻止任何可能发生的奇怪结果除此以外。
  • angular落按逆时针顺序列出。 你可以使它为任何顺序工作,但它觉得像一个公平的约束添加简单。

如果你想的话,只要它是严格凸起的(即不是尖尖的星形),就可以使用相同的技术来圆化任何多边形。 我不会解释怎么做,但它遵循相同的原则。


这一切都开始于一个三angular形,你想绕一个半径的angular落, r

在这里输入图像说明

圆angular三angular形应该包含在尖angular三angular形中,所以第一步是find位置,尽可能靠近angular落,在那里你可以适应一个半径为r的圆。

一个简单的方法是创build三条与三angular形平行的新线条,并将距离r向内移动,正交于原边的一边。

要做到这一点,你可以计算每条线的斜率/angular度以及应用于两个新点的偏移量:

 CGFloat angle = atan2f(end.y - start.y, end.x - start.x); CGVector offset = CGVectorMake(-sinf(angle)*radius, cosf(angle)*radius); 

注意:为了清晰起见,我使用了CGVectortypes(在iOS 7中可用),但您也可以使用点或大小来处理以前的操作系统版本

然后将偏移量添加到每行的开始点和结束点:

 CGPoint offsetStart = CGPointMake(start.x + offset.dx, start.y + offset.dy); CGPoint offsetEnd = CGPointMake(end.x + offset.dx, end.y + offset.dy); 

当你这样做的时候,你会看到三条线相交于三个地方:

在这里输入图像说明

每个交点恰好与两侧的距离r (假设三angular形足够大,如上所述)

你可以计算两条线的交点:

 // (x1⋅y2-y1⋅x2)(x3-x4) - (x1-x2)(x3⋅y4-y3⋅x4) // px = ––––––––––––––––––––––––––––––––––––––––––– // (x1-x2)(y3-y4) - (y1-y2)(x3-x4) // (x1⋅y2-y1⋅x2)(y3-y4) - (y1-y2)(x3⋅y4-y3⋅x4) // py = ––––––––––––––––––––––––––––––––––––––––––– // (x1-x2)(y3-y4) - (y1-y2)(x3-x4) CGFloat intersectionX = ((x1*y2-y1*x2)*(x3-x4) - (x1-x2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4) - (y1-y2)*(x3-x4)); CGFloat intersectionY = ((x1*y2-y1*x2)*(y3-y4) - (y1-y2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4) - (y1-y2)*(x3-x4)); CGPoint intersection = CGPointMake(intersectionX, intersectionY); 

其中(x1,y1)到(x2,y2)是第一行,(x3,y3)到(x4,y4)是第二行。

如果你在每个交点上放一个半径为r的圆,你可以看到圆的三angular形(忽略三angular形和圆的不同线宽):

在这里输入图像说明

现在要创build一个圆angular三angular形,您要创build一条从一条直线到一条弧的path,直到原三angular形与交点正交的点(如等)。 这也是圆的原点三angular形的切点。

知道三angular形中所有三条边的斜率,拐angular半径和圆的中心(交点),每个圆angular的起始和终止angular是该边的斜率 – 90度。 为了将这些东西组合在一起,我在代码中创build了一个结构,但是如果您不想要:

 typedef struct { CGPoint centerPoint; CGFloat startAngle; CGFloat endAngle; } CornerPoint; 

为了减less代码重复,我为自己创build了一个方法,它可以计算一个点的交点和angular度,从一个点到另一个点的最后一个点(它不是闭合的,所以不是三angular形):

在这里输入图像说明

代码如下(这实际上只是我上面显示的代码,放在一起):

 - (CornerPoint)roundedCornerWithLinesFrom:(CGPoint)from via:(CGPoint)via to:(CGPoint)to withRadius:(CGFloat)radius { CGFloat fromAngle = atan2f(via.y - from.y, via.x - from.x); CGFloat toAngle = atan2f(to.y - via.y, to.x - via.x); CGVector fromOffset = CGVectorMake(-sinf(fromAngle)*radius, cosf(fromAngle)*radius); CGVector toOffset = CGVectorMake(-sinf(toAngle)*radius, cosf(toAngle)*radius); CGFloat x1 = from.x +fromOffset.dx; CGFloat y1 = from.y +fromOffset.dy; CGFloat x2 = via.x +fromOffset.dx; CGFloat y2 = via.y +fromOffset.dy; CGFloat x3 = via.x +toOffset.dx; CGFloat y3 = via.y +toOffset.dy; CGFloat x4 = to.x +toOffset.dx; CGFloat y4 = to.y +toOffset.dy; CGFloat intersectionX = ((x1*y2-y1*x2)*(x3-x4) - (x1-x2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4) - (y1-y2)*(x3-x4)); CGFloat intersectionY = ((x1*y2-y1*x2)*(y3-y4) - (y1-y2)*(x3*y4-y3*x4)) / ((x1-x2)*(y3-y4) - (y1-y2)*(x3-x4)); CGPoint intersection = CGPointMake(intersectionX, intersectionY); CornerPoint corner; corner.centerPoint = intersection; corner.startAngle = fromAngle - M_PI_2; corner.endAngle = toAngle - M_PI_2; return corner; } 

然后,我用这个代码3次来计算三个angular落:

 CornerPoint leftCorner = [self roundedCornerWithLinesFrom:right via:left to:top withRadius:radius]; CornerPoint topCorner = [self roundedCornerWithLinesFrom:left via:top to:right withRadius:radius]; CornerPoint rightCorner = [self roundedCornerWithLinesFrom:top via:right to:left withRadius:radius]; 

现在,拥有所有必要的数据,开始创build实际path的部分。 我要依靠的事实是,CGPathAddArc将添加一条从当前点到起点的直线,而不必自己绘制这些线(这是logging的行为)。

我手动计算的唯一的一点是path的起点。 我select右下angular的开始(没有具体的原因)。 从那里你只需要在起点和终点的交点处添加一个以中心为圆心的圆弧:

 CGMutablePathRef roundedTrianglePath = CGPathCreateMutable(); // manually calculated start point CGPathMoveToPoint(roundedTrianglePath, NULL, leftCorner.centerPoint.x + radius*cosf(leftCorner.startAngle), leftCorner.centerPoint.y + radius*sinf(leftCorner.startAngle)); // add 3 arcs in the 3 corners CGPathAddArc(roundedTrianglePath, NULL, leftCorner.centerPoint.x, leftCorner.centerPoint.y, radius, leftCorner.startAngle, leftCorner.endAngle, NO); CGPathAddArc(roundedTrianglePath, NULL, topCorner.centerPoint.x, topCorner.centerPoint.y, radius, topCorner.startAngle, topCorner.endAngle, NO); CGPathAddArc(roundedTrianglePath, NULL, rightCorner.centerPoint.x, rightCorner.centerPoint.y, radius, rightCorner.startAngle, rightCorner.endAngle, NO); // close the path CGPathCloseSubpath(roundedTrianglePath); 

看起来像这样:

在这里输入图像说明

没有所有支持线的最终结果如下所示:

在这里输入图像说明

David的几何很酷,很有教育意义。 但是你并不需要这样通过整个几何。 我会提供一个更简单的代码:

 CGFloat radius = 20; CGMutablePathRef path = CGPathCreateMutable(); CGPathMoveToPoint(path, NULL, (center.x + bottomLeft.x) / 2, (center.y + bottomLeft.y) / 2); CGPathAddArcToPoint(path, NULL, bottomLeft.x, bottomLeft.y, bottomRight.x, bottomRight.y, radius); CGPathAddArcToPoint(path, NULL, bottomRight.x, bottomRight.y, center.x, center.y, radius); CGPathAddArcToPoint(path, NULL, center.x, center.y, bottomLeft.x, bottomLeft.y, radius); CGPathCloseSubpath(path); UIBezierPath *bezierPath = [UIBezierPath bezierPathWithCGPath:path]; CGPathRelease(path); 

bezierPath是你需要的。 关键是CGPathAddArcToPoint为您做几何。

Swift 4中的圆angular三angular形

根据上面的@ an0的回答 :

 override func viewDidLoad() { let triangle = CAShapeLayer() triangle.fillColor = UIColor.lightGray.cgColor triangle.path = createRoundedTriangle(width: 200, height: 200, radius: 8) triangle.position = CGPoint(x: 200, y: 300) view.layer.addSublayer(triangle) } func createRoundedTriangle(width: CGFloat, height: CGFloat, radius: CGFloat) -> CGPath { // Points of the triangle let point1 = CGPoint(x: -width / 2, y: height / 2) let point2 = CGPoint(x: 0, y: -height / 2) let point3 = CGPoint(x: width / 2, y: height / 2) let path = CGMutablePath() path.move(to: CGPoint(x: 0, y: height / 2)) path.addArc(tangent1End: point1, tangent2End: point2, radius: radius) path.addArc(tangent1End: point2, tangent2End: point3, radius: radius) path.addArc(tangent1End: point3, tangent2End: point1, radius: radius) path.closeSubpath() return path } 

在这里输入图像说明

创build三angular形作为UIBezierpath

 let cgPath = createRoundedTriangle(width: 200, height: 200, radius: 8) let path = UIBezierPath(cgPath: cgPath) 

@ an0 – 谢谢! 我是全新的,所以我没有名誉来标记有用或评论,但今天你节省了我大量的时间。

我知道这是超出了问题的范围,但同样的逻辑适用于CGContextBeginPath()CGContextMoveToPoint()CGContextAddArcToPoint()CGContextClosePath() ,以防万一需要使用它。

如果任何人有兴趣,这是我的代码等边三angular指向右侧。 triangleFrame是一个预定义的CGRectradius是一个预定义的CGFloat

 CGContextRef context = UIGraphicsGetCurrentContext(); CGContextBeginPath(context); CGContextMoveToPoint(context, CGRectGetMinX(triangleFrame), CGRectGetMidY(triangleFrame)); CGRectGetMidY(triangleFrame)); CGContextAddArcToPoint(context, CGRectGetMinX(triangleFrame), CGRectGetMidY(triangleFrame), CGRectGetMinX(triangleFrame), CGRectGetMinY(triangleFrame), radius); CGContextAddArcToPoint(context, CGRectGetMinX(triangleFrame), CGRectGetMinY(triangleFrame), CGRectGetMaxX(triangleFrame), CGRectGetMidY(triangleFrame), radius); CGContextAddArcToPoint(context, CGRectGetMaxX(triangleFrame), CGRectGetMidY(triangleFrame), CGRectGetMinX(triangleFrame), CGRectGetMaxY(triangleFrame), radius); CGContextAddArcToPoint(context, CGRectGetMinX(triangleFrame), CGRectGetMaxY(triangleFrame), CGRectGetMinX(triangleFrame), CGRectGetMidY(triangleFrame), radius); 

当然,每个点可以更具体地定义为不规则的三angular形。 您可以根据需要然后CGContextFillPath()CGContextStrokePath()

更简单得多的只是添加两行代码:

 UIBezierPath *bezierPath = [UIBezierPath bezierPath]; bezierPath.lineCapStyle = kCGLineCapRound; bezierPath.lineJoinStyle = kCGLineJoinRound; CGPoint center = CGPointMake(rect.size.width / 2, 0); ... 

编辑:

要看到angular落是圆的,你需要使三angular形比矩形边界更小一些:

  CGPoint center = CGPointMake(rect.size.width / 2, 10); CGPoint bottomLeft = CGPointMake(10, rect.size.height - 10); CGPoint bottomRight = CGPointMake(rect.size.width - 10, rect.size.height - 10); 

基于UIBezierPath的回答,我写了一个小小的UIBezierPath扩展,增加了与CGPath相同的function:

 extension UIBezierPath { // Based on https://stackoverflow.com/a/20646446/634185 public func addArc(from startPoint: CGPoint, to endPoint: CGPoint, centerPoint: CGPoint, radius: CGFloat, clockwise: Bool) { let fromAngle = atan2(centerPoint.y - startPoint.y, centerPoint.x - startPoint.x) let toAngle = atan2(endPoint.y - centerPoint.y, endPoint.x - centerPoint.x) let fromOffset = CGVector(dx: -sin(fromAngle) * radius, dy: cos(fromAngle) * radius) let toOffset = CGVector(dx: -sin(toAngle) * radius, dy: cos(toAngle) * radius) let x1 = startPoint.x + fromOffset.dx let y1 = startPoint.y + fromOffset.dy let x2 = centerPoint.x + fromOffset.dx let y2 = centerPoint.y + fromOffset.dy let x3 = centerPoint.x + toOffset.dx let y3 = centerPoint.y + toOffset.dy let x4 = endPoint.x + toOffset.dx let y4 = endPoint.y + toOffset.dy let intersectionX = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)) let intersectionY = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)) let intersection = CGPoint(x: intersectionX, y: intersectionY) let startAngle = fromAngle - .pi / 2.0 let endAngle = toAngle - .pi / 2.0 addArc(withCenter: intersection, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: clockwise) } } 

所以,这个代码应该是这样的:

 let path = UIBezierPath() let center = CGPoint(x: rect.size.width / 2, y: 0) let bottomLeft = CGPoint(x: 10, y: rect.size.height - 0) let bottomRight = CGPoint(x: rect.size.width - 0, y: rect.size.height - 0) path.move(to: center); path.addArc(from: center, to: bottomRight, centerPoint: bottomLeft, radius: radius, clockwise: false) path.addLine(to: bottomRight) path.close()