UIBezierPath三角形,圆边

H B*_*amy 21 core-graphics objective-c cashapelayer ios uibezierpath

我设计了这个代码来生成一个Bezier路径,用作CAShapeLayer掩盖UIView的路径(视图的高度和宽度是可变的)

这段代码生成一个边缘锐利的三角形,但我想让它成为一个圆角!我花了好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];
Run Code Online (Sandbox Code Playgroud)

所以我的问题是,如何在UIBezierPath中舍入三角形的所有边缘(我需要子层,多路径等)吗?

NB我没有画这个BezierPath所以所有的CGContext...功能drawRect都无法帮助我:(

谢谢!

Dav*_*ist 108

编辑

FWIW:这个答案通过解释CGPathAddArcToPoint(...)为您做什么来达到其教育目的.我强烈建议您仔细阅读,因为它将帮助您理解和欣赏CGPath API.然后你应该继续使用它,如an0的答案中所示,而不是在你的应用程序中使用边缘时使用此代码.如果您想要使用此代码并且了解这样的几何计算,则此代码应仅用作参考.


原始答案

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

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


注意:为了我自己的简单,我的解决方案是对用于形成三角形的点做出一些假设,例如:

  • 三角形的面积足以适合圆角(例如,三角形的高度大于角落中圆圈的直径.我没有检查或试图防止可能发生的任何奇怪结果除此以外.
  • 拐角按逆时针顺序列出.您可以使它适用于任何订单,但为了简单起见,它感觉像是一个公平的约束.

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


这一切都以一个三角形开始,你想要用一些半径圆角,r:

在此输入图像描述

圆角三角形应包含在尖三角形中,因此第一步是找到尽可能靠近角落的位置,在这里您可以使用半径r来拟合圆.

这样做的一种简单方法是在三角形中创建3条与3条边平行的新线,并将每条距离r向内移动,与原边的侧面正交.

为此,您需要计算每条线的斜率/角度以及要应用于两个新点的偏移量:

CGFloat angle = atan2f(end.y - start.y,
                       end.x - start.x);

CGVector offset = CGVectorMake(-sinf(angle)*radius,
                                cosf(angle)*radius);
Run Code Online (Sandbox Code Playgroud)

注意:为了清楚起见,我使用的是CGVector类型(在iOS 7中可用),但您也可以使用点或大小来使用以前的OS版本.

然后将偏移量添加到每行的起点和终点:

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

CGPoint offsetEnd   = CGPointMake(end.x + offset.dx,
                                  end.y + offset.dy);
Run Code Online (Sandbox Code Playgroud)

当你这样做时,你会看到三条线在三个地方相互交叉:

在此输入图像描述

每个交叉点恰好是两个边的距离r (假设三角形足够大,如上所述).

您可以将两条线的交点计算为:

//       (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);
Run Code Online (Sandbox Code Playgroud)

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

如果然后在每个交点上放置一个半径为r的圆,则可以看到它确实是圆角三角形(忽略三角形和圆形的不同线宽):

在此输入图像描述

现在要创建一个圆角三角形,您要创建一条路径,该路径从一条线变为一条弧,再到原始三角形与交叉点正交的点上的一条线(等).这也是圆与原始三角形相切的点.

知道三角形中所有3个边的斜率,圆角半径和圆的中心(交点),每个圆角的起始和终止角度是该边的斜率--90度.为了将这些东西组合在一起,我在我的代码中创建了一个结构,但如果你不想这样做,则不必:

typedef struct {
    CGPoint centerPoint;
    CGFloat startAngle;
    CGFloat endAngle;
} CornerPoint;
Run Code Online (Sandbox Code Playgroud)

为了减少代码重复,我为自己创建了一个方法,计算一个点的交点和角度,给定从一个点到另一个点的线到最终点(它不是关闭的,所以它不是三角形):

在此输入图像描述

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

- (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;
}
Run Code Online (Sandbox Code Playgroud)

然后我使用该代码3次来计算3个角:

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];
Run Code Online (Sandbox Code Playgroud)

现在,拥有所有必要的数据,启动我们创建实际路径的部分.我将依赖于CGPathAddArc将从当前点添加到起点的直线而不必自己绘制这些行(这是记录的行为).

我手动必须计算的唯一点是路径的起点.我选择右下角的开头(没有具体原因).从那里你只需添加一个圆弧,中心位于从起点和终止角度的交点:

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); 
Run Code Online (Sandbox Code Playgroud)

看起来像这样:

在此输入图像描述

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

在此输入图像描述


an0*_*an0 36

@ 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);
Run Code Online (Sandbox Code Playgroud)

bezierPath是你需要的.关键是CGPathAddArcToPoint你的几何形状.


sid*_*ick 15

Swift 4中的圆角三角形

在此输入图像描述

基于@ 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 {
    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
}
Run Code Online (Sandbox Code Playgroud)

将三角形创建为UIBezierPath

let cgPath = createRoundedTriangle(width: 200, height: 200, radius: 8)
let path = UIBezierPath(cgPath: cgPath)
Run Code Online (Sandbox Code Playgroud)