ios - 带圆角的UIBezierPath三角形

标签 ios objective-c core-graphics uibezierpath cashapelayer

我已设计此代码来生成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];


所以我的问题是,我将如何对UIBezierPath中的三角形的所有边缘进行四舍五入(我是否需要子层,多个路径等)?

N.B我没有绘制此BezierPath,所以CGContext...中的所有drawRect函数都无法帮助我:(

谢谢!

最佳答案

编辑

FWIW:此答案通过解释CGPathAddArcToPoint(...)对您的作用来达到其教育目的。我强烈建议您通读它,因为它可以帮助您理解和欣赏CGPath API。然后,您应该继续使用它,如在an0's answer中看到的那样,当在应用程序中倒圆角时,请使用此代码,而不要使用此代码。仅当您想尝试并了解这样的几何计算时,才应将此代码用作参考。



原始答案

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

这是一个很长的答案。没有简短的版本: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);


注意:为清楚起见,我使用的是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);


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



每个相交点恰好是与两个侧面之间的距离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);


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

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

 

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

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

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


为了减少代码重复,我为自己创建了一种方法,该方法计算给定一条线从一个点到另一个点(到最后一个点)的交点和角度(该点未闭合,因此不是三角形):



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

- (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次来计算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];


现在,拥有所有必要的数据,开始创建实际路径的部分。我将依靠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); 


看起来像这样:

  

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

关于ios - 带圆角的UIBezierPath三角形,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47164784/

相关文章:

ios - 平滑地将渐变应用到 UIImage

iOS:应用程序主页按钮快捷方式

ios - 无法从 plist 获取内容到 UITableview

objective-c - NSImage 的 CIAreaHistogram/CIHistogramDisplayFilter 工作示例

ios - CoreText 替代已弃用的 CGContext 文本函数

ios - 绘制到 CGContext 时如何设置 UIBezierPath 的线宽?

ios - 在模型- View - Controller 中, View 是否可以依赖于模型?

ios - Q : Search Bar and Search Display Controller in storyboard with swift and Xcode8. 0 Appdelegate 崩溃

objective-c - 使用 block 会导致 EXC_BAD_ACCESS

objective-c - Objective-C 2.0 中的类对象是什么?