UIBezierPath三角形,圆边 [英] UIBezierPath Triangle with rounded edges

查看:182
本文介绍了UIBezierPath三角形,圆边的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have designed this code to generate a Bezier Path that is used as a path for a CAShapeLayer to mask a UIView (the view's height and width are variable)

这段代码生成一个边缘锐利的三角形,但我想让它成为一个圆形的角落!我花了2个小时尝试使用 addArcWithCenter ... lineCapStyle lineJoinStyle 等但似乎没有什么对我有用。

This code generates a triangle with sharp edges, but I would like to make it round cornered! I have spent a good 2 hours trying to work with addArcWithCenter..., lineCapStyle and lineJoinStyle etc but nothing seems to work for me.

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中舍入三角形的所有边缘(我需要子层,多路径等)?

So my question is, how would I go about rounding off ALL the edges of a triangle in a UIBezierPath (Do I need sublayers, multiple paths etc)?

NB我没有绘制这个BezierPath所以 CGContext ... 函数 drawRect 无法帮助我:(

N.B I am NOT drawing this BezierPath so all the CGContext... functions in drawRect can't help me :(

谢谢!

推荐答案

编辑



FWIW:此答案符合其教育目的解释什么 CGPathAddArcToPoint(...)为你做的。我强烈建议你仔细阅读它,因为它将帮助你理解和欣赏CGPath API。然后你应该去提前并使用它,如 an0的答案中所示,而不是在您的应用程序中使用边缘时此代码。此代码应该只有当你想要玩这个并且学习这样的几何计算时才能用作参考。

Edit

FWIW: This answer serves its educational purpose by explaining what CGPathAddArcToPoint(...) does for you. I would highly recommend that you read through it as it will help you understand and appreciate the CGPath API. Then you should go ahead and use that, as seen in an0's answer, instead of this code when you round edges in your app. This code should only be used as a reference if you want to play around with and learn about geometry calculations like this.

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

Because I find questions like this so fun, I had to answer it :)

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

This is a long answer. There is no short version :D

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


  • 三角形的面积足够大适合圆角(例如,三角形的高度大于角落中圆圈的直径。我没有检查或试图阻止任何可能发生的奇怪结果。

  • 角落以逆时针顺序列出。你可以让它适用于任何订单但是为了简单起见,它感觉像是一个足够公平的约束。

  • The area of the triangle is large enough to fit the rounded corner (e.g. the height of the triangle is greater than the diameter of the circles in the corners. I'm not checking for or trying to prevent any kind of strange results that may happen otherwise.
  • The corners are listed in counter clock-wise order. You could make it work for any order but it felt like a fair enough constraint to add for simplicity.

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

It所有的三角形开始,你想绕着一些半径转弯, r

It all starts of with a triangle, that you want to round the corners of with some radius, r:

圆角三角形应该包含在尖三角形中,所以第一步是找到位置,如尽可能靠近角落,你可以在这里安装一个半径为r的圆圈。

The rounded triangle should be contained in the pointy triangle so the first step is to find the locations, as close to the corners as possible, where you can fit a circle with the radius, r.

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

A simple way of doing this is to create 3 new lines parallel to the 3 sides in the triangle and shift each of the the distance r inwards, orthogonal to the side of the original side.

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

To do this you calculate the slope/angle of each line and the offset to apply to the two new points:

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

CGVector offset = CGVectorMake(-sinf(angle)*radius,
                                cosf(angle)*radius);

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

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

then you add the offset to both start and end points for each line:

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

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

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

When you do tho you will see that the three lines intersect each other in three places:

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

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

You can calculate the intersection of two lines as:

//       (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)是第二行。

where (x1, y1) to (x2, y2) is the first line and (x3, y3) to (x4, y4) is the second line.

如果你在每个交点上放一个半径 r 的圆圈,你会发现它确实是圆角三角形(忽略三角形和圆圈的不同线宽):

If you then put a circle, with the radius r, on each intersection point you can see that it will indeed for the rounded triangle (ignoring the different line widths of the triangle and the circles):

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

Now to create the rounded triangle you want to create a path that changes from a line to an arc to a line (etc.) on the points where the original triangle is orthogonal to the intersection points. This is also the point where the circles tangent the original triangle.

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

Knowing the slopes of all 3 sides in the triangle, the corner radius and the center of the circles (the intersection points), the start and stop angle for each rounded corner is the slope of that side - 90 degrees. To group these things together, I created a struct in my code, but you don't have to if you don't want to:

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

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

To reduce code duplication I created a method for myself that calculates the intersection and the angles for one point given a line from one point, via another, to a final point (it's not closed so it's not a triangle):

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

The code is as follows (it's really just the code that I've shown above, put together):

- (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个角:

I then used that code 3 times to calculate the 3 corners:

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将从当前点添加到起点的直线,而不必自己绘制这些行(这是记录的行为)。

Now, having all the necessary data, starts the part where we create the actual path. I'm going to rely on the fact that CGPathAddArc will add a straight line from the current point to the start point to not have to draw those lines myself (this is documented behaviour).

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

The only point I manually have to calculate is the start point of the path. I choose the start of the lower right corner (no specific reason). From there you just add an arc with the center in the intersection points from the start and end angles:

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); 

看起来像这样:

< img src =https://i.stack.imgur.com/8YGUX.pngalt =在此输入图像说明>

最终结果没有全部支持行,如下所示:

The final result without all the support lines, look like this:

这篇关于UIBezierPath三角形,圆边的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆