I have designed the following code to generate a UIBezierPath
. This path is used within a CAShapeLayer
to mask a UIView
. Note, the view's height and width are variable.
This code generates a triangle with sharp edges, but I would like to make the corners rounded. I have experimented 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];
How do I round all the edges of a triangle in a UIBezierPath
? Do I need sublayers, multiple paths etc?
I am not drawing this BezierPath so all the CGContext...
functions in drawRect
are not useful in this context
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.Original answer
Because I find questions like this so fun, I had to answer it :)
This is a long answer. There is no short version :D
Note: For my own simplicity, my solution is making some assumptions about the points that are being used to form the triangle such as:
If you wanted, you could use the same technique to round any polygon as long as it's strictly convex (i.e. not a pointy star). I won't explain how to do it though but it follows the same principle.
It all starts of with a triangle, that you want to round the corners of with some radius, 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.
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:
Note: for clarity I'm using the CGVector type (available in iOS 7), but you can just as well use a point or a size to work with previous OS versions.
then you add the offset to both start and end points for each line:
When you do tho you will see that the three lines intersect each other in three places:
Each intersection point is exactly the distance r from two of the sides (assuming that the triangle is large enough, as stated above).
You can calculate the intersection of two lines as:
where (x1, y1) to (x2, y2) is the first line and (x3, y3) to (x4, y4) is the second line.
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.
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:
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):
I then used that code 3 times to calculate the 3 corners:
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:
Looking something like this:
The final result without all the support lines, look like this: