UIBezierPath Triangle with rounded edges

Learn uibezierpath triangle with rounded edges with practical examples, diagrams, and best practices. Covers ios, objective-c, core-graphics development techniques with visual explanations.

Crafting a UIBezierPath Triangle with Rounded Edges in iOS

Hero image for UIBezierPath Triangle with rounded edges

Learn how to draw a custom triangle shape with perfectly rounded corners using UIBezierPath and CAShapeLayer in Objective-C for iOS applications.

Creating custom shapes in iOS is a common requirement for unique UI designs. While UIBezierPath excels at drawing basic shapes, achieving rounded corners on a triangle's sharp vertices requires a specific approach. This article will guide you through the process of constructing a triangle with rounded edges using UIBezierPath and CAShapeLayer in Objective-C, providing a robust solution for custom UI elements.

Understanding the Challenge: Rounding Triangle Corners

A standard triangle drawn with UIBezierPath using addLineToPoint: will have sharp, pointed corners. Directly applying a corner radius to a CAShapeLayer only rounds the layer's bounding box, not the path itself. To achieve rounded corners on the triangle's vertices, we need to manually construct the path by drawing arcs at each corner. This involves calculating the start and end points for each arc and the center point for the arc's radius.

flowchart TD
    A[Define Triangle Vertices] --> B{Calculate Corner Radii Points}
    B --> C[Move to Start Point of First Arc]
    C --> D[Add Arc for First Corner]
    D --> E[Add Line to Start Point of Second Arc]
    E --> F[Add Arc for Second Corner]
    F --> G[Add Line to Start Point of Third Arc]
    G --> H[Add Arc for Third Corner]
    H --> I[Close Path]
    I --> J[Apply to CAShapeLayer]
    J --> K[Display on UIView]

Workflow for drawing a rounded triangle using UIBezierPath.

Calculating Arc Points for Rounded Corners

The key to rounding the corners is to replace the sharp vertex with a small arc. For each corner, you need to determine two points along the adjacent edges that are radius distance away from the vertex. These two points will serve as the start and end points of your arc. The arc itself will be drawn around the vertex, effectively cutting off the sharp point. This requires some basic geometry to find the intersection points and the center of the arc.

#import <UIKit/UIKit.h>

@interface RoundedTriangleView : UIView

@property (nonatomic, strong) UIColor *fillColor;
@property (nonatomic, assign) CGFloat cornerRadius;

@end

@implementation RoundedTriangleView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor clearColor];
        _fillColor = [UIColor blueColor];
        _cornerRadius = 10.0f;
    }
    return self;
}

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];

    // Define the three vertices of the triangle
    CGPoint p1 = CGPointMake(rect.size.width / 2.0, 0);
    CGPoint p2 = CGPointMake(rect.size.width, rect.size.height);
    CGPoint p3 = CGPointMake(0, rect.size.height);

    UIBezierPath *path = [UIBezierPath bezierPath];

    // Calculate points for rounded corners
    CGFloat radius = self.cornerRadius;

    // Corner 1 (p1)
    CGPoint p1_start = CGPointMake(p1.x + radius * cos(M_PI * 1.5), p1.y + radius * sin(M_PI * 1.5)); // Point on edge p1-p3
    CGPoint p1_end = CGPointMake(p1.x + radius * cos(M_PI * 0.5), p1.y + radius * sin(M_PI * 0.5));   // Point on edge p1-p2

    // Corner 2 (p2)
    CGPoint p2_start = CGPointMake(p2.x - radius, p2.y); // Point on edge p2-p1
    CGPoint p2_end = CGPointMake(p2.x, p2.y - radius); // Point on edge p2-p3

    // Corner 3 (p3)
    CGPoint p3_start = CGPointMake(p3.x, p3.y - radius); // Point on edge p3-p2
    CGPoint p3_end = CGPointMake(p3.x + radius, p3.y); // Point on edge p3-p1

    // Start drawing the path
    [path moveToPoint:p1_start];

    // Arc for p1
    [path addArcWithCenter:p1 radius:radius startAngle:M_PI * 1.5 endAngle:M_PI * 0.5 clockwise:YES];

    // Line to p2_start
    [path addLineToPoint:p2_start];

    // Arc for p2
    [path addArcWithCenter:p2 radius:radius startAngle:M_PI * 0.5 endAngle:M_PI * 1.5 clockwise:YES];

    // Line to p3_start
    [path addLineToPoint:p3_start];

    // Arc for p3
    [path addArcWithCenter:p3 radius:radius startAngle:M_PI * 1.5 endAngle:M_PI * 0.5 clockwise:YES];

    [path closePath];

    // Fill the path
    [self.fillColor setFill];
    [path fill];

    // Optionally, add a border
    // [[UIColor blackColor] setStroke];
    // [path setLineWidth:2.0];
    // [path stroke];
}

@end

Objective-C code for a UIView subclass that draws a rounded triangle.

Integrating with CAShapeLayer

For better performance and flexibility, especially with animations, it's often preferred to use CAShapeLayer instead of drawing directly in drawRect:. A CAShapeLayer takes a CGPath (which can be derived from UIBezierPath) and renders it. This separates the drawing logic from the view's drawRect: method, allowing for more efficient updates and animations.

#import <UIKit/UIKit.h>

@interface RoundedTriangleLayer : CAShapeLayer

- (void)setupTriangleInRect:(CGRect)rect withCornerRadius:(CGFloat)cornerRadius fillColor:(UIColor *)fillColor;

@end

@implementation RoundedTriangleLayer

- (void)setupTriangleInRect:(CGRect)rect withCornerRadius:(CGFloat)cornerRadius fillColor:(UIColor *)fillColor {
    // Define the three vertices of the triangle
    CGPoint p1 = CGPointMake(rect.size.width / 2.0, 0);
    CGPoint p2 = CGPointMake(rect.size.width, rect.size.height);
    CGPoint p3 = CGPointMake(0, rect.size.height);

    UIBezierPath *path = [UIBezierPath bezierPath];

    CGFloat radius = cornerRadius;

    // Corner 1 (p1)
    CGPoint p1_start = CGPointMake(p1.x + radius * cos(M_PI * 1.5), p1.y + radius * sin(M_PI * 1.5)); // Point on edge p1-p3
    CGPoint p1_end = CGPointMake(p1.x + radius * cos(M_PI * 0.5), p1.y + radius * sin(M_PI * 0.5));   // Point on edge p1-p2

    // Corner 2 (p2)
    CGPoint p2_start = CGPointMake(p2.x - radius, p2.y); // Point on edge p2-p1
    CGPoint p2_end = CGPointMake(p2.x, p2.y - radius); // Point on edge p2-p3

    // Corner 3 (p3)
    CGPoint p3_start = CGPointMake(p3.x, p3.y - radius); // Point on edge p3-p2
    CGPoint p3_end = CGPointMake(p3.x + radius, p3.y); // Point on edge p3-p1

    // Start drawing the path
    [path moveToPoint:p1_start];

    // Arc for p1
    [path addArcWithCenter:p1 radius:radius startAngle:M_PI * 1.5 endAngle:M_PI * 0.5 clockwise:YES];

    // Line to p2_start
    [path addLineToPoint:p2_start];

    // Arc for p2
    [path addArcWithCenter:p2 radius:radius startAngle:M_PI * 0.5 endAngle:M_PI * 1.5 clockwise:YES];

    // Line to p3_start
    [path addLineToPoint:p3_start];

    // Arc for p3
    [path addArcWithCenter:p3 radius:radius startAngle:M_PI * 1.5 endAngle:M_PI * 0.5 clockwise:YES];

    [path closePath];

    self.path = path.CGPath;
    self.fillColor = fillColor.CGColor;
    self.strokeColor = [UIColor clearColor].CGColor;
    self.lineWidth = 0;
}

@end

// How to use in a UIView:
// RoundedTriangleLayer *triangleLayer = [RoundedTriangleLayer layer];
// [triangleLayer setupTriangleInRect:self.bounds withCornerRadius:15.0f fillColor:[UIColor redColor]];
// [self.layer addSublayer:triangleLayer];

Using CAShapeLayer to render the rounded triangle path.

Considerations for Dynamic Triangles

The provided code assumes an isosceles triangle with its apex at the top center. For more complex or dynamic triangle shapes (e.g., scalene triangles, different orientations), the calculation of the arc start/end points and centers will become more involved. You would need to use vector math to determine the angles and intersection points accurately. However, the core principle of replacing sharp vertices with arcs remains the same.

1. Define Triangle Vertices

Start by defining the three CGPoint vertices of your triangle. These will be the conceptual 'corners' before rounding.

2. Calculate Corner Arc Parameters

For each vertex, determine the two points on the adjacent edges that are cornerRadius distance away from the vertex. These will be the start and end points for your arc. Also, identify the center point for each arc (which will be the original vertex itself for simple cases).

3. Construct UIBezierPath

Begin the UIBezierPath by moving to the start point of the first arc. Then, add the arc using addArcWithCenter:radius:startAngle:endAngle:clockwise:. After each arc, add a addLineToPoint: to connect to the start of the next arc. Finally, call closePath to complete the shape.

4. Apply to CAShapeLayer

Set the path property of a CAShapeLayer to your UIBezierPath's CGPath. Configure fillColor, strokeColor, and lineWidth as needed, then add the CAShapeLayer as a sublayer to your UIView's layer.