Mobile Development 21 min read

Mastering Bezier Curves and Layer Animations in iOS: From Modeling to Rendering

This tutorial explains how to model, generate, and render Bézier curves on iOS using UIBezierPath, CALayer, CAShapeLayer, and CAGradientLayer, compares quadratic and cubic approaches with Catmull‑Rom interpolation, and provides complete code samples for creating dynamic, animated graphics.

Beike Product & Technology
Beike Product & Technology
Beike Product & Technology
Mastering Bezier Curves and Layer Animations in iOS: From Modeling to Rendering

Introduction

In the era of big data, visualizing information such as historical prices, growth trends, and proportion charts often requires drawing graphics programmatically. This article explains the modeling and rendering steps needed to create Bézier‑based graphics on iOS.

2 Modeling Bézier Curves

Bézier curves are the mathematical foundation for most 2‑D vector graphics on the client side. The curve is defined by a start point, an end point, and one or more control points. According to the number of control points, the curve can be classified as:

First‑order (no control point)

Second‑order (one control point)

Third‑order (two control points)

N‑order (n‑1 control points)

2.1 Principle of a Quadratic Bézier Curve

Using a second‑order curve as an example, the points are labeled P0 (start), P1 (control), and P2 (end). The construction steps are:

Connect P0‑P1 and P1‑P2.

Find point A on P0‑P1 and point B on P1‑P2 such that P0A / AP1 = P1B / BP2.

Connect A and B, then locate point X on AB that satisfies AX / XB = P0A / AP1 = P1B / BP2.

All points X that satisfy the equation form the Bézier curve between P0 and P2.

Similar constructions apply to cubic and higher‑order curves, as illustrated by the following images:

2.2 UIBezierPath Class

iOS provides the UIBezierPath class for creating vector shapes. Commonly used methods include:

// Create a UIBezierPath object
+ (instancetype)bezierPath;
// Rectangle inside a rect
+ (instancetype)bezierPathWithRect:(CGRect)rect;
// Rounded rectangle with specific corners
+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii;
// Arc
+ (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
// Add cubic Bézier curve
- (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
// Add quadratic Bézier curve
- (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;

Typical drawing commands are:

// Move to a point
- (void)moveToPoint:(CGPoint)point;
// Draw a line
- (void)addLineToPoint:(CGPoint)point;
// Close the path
- (void)closePath;
// Fill the shape
- (void)fill;
// Stroke the path
- (void)stroke;

Key properties include CGPath, lineWidth, lineCapStyle, and lineJoinStyle.

2.2.4 Example

The following code draws three rectangles with different styles:

- (void)drawRect:(CGRect)rect {
    [[UIColor redColor] set];
    // First rectangle – stroked only
    UIBezierPath* maskPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 100, 100) byRoundingCorners:UIRectCornerTopLeft cornerRadii:CGSizeMake(30, 30)];
    maskPath.lineWidth = 20.f;
    maskPath.lineJoinStyle = kCGLineJoinBevel;
    [maskPath stroke];
    // Second rectangle – filled and stroked
    UIBezierPath* maskFillPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(200, 50, 100, 100) byRoundingCorners:UIRectCornerTopLeft cornerRadii:CGSizeMake(30, 30)];
    maskFillPath.lineWidth = 20.f;
    maskFillPath.lineJoinStyle = kCGLineJoinBevel;
    [maskFillPath fill];
    [maskFillPath stroke];
    // Third shape – simple line
    UIBezierPath *maskLinePath = [UIBezierPath bezierPath];
    maskLinePath.lineWidth = 20.f;
    maskLinePath.lineCapStyle = kCGLineCapRound;
    [maskLinePath moveToPoint:CGPointMake(250.0, 50)];
    [maskLinePath addLineToPoint:CGPointMake(300.0, 100)];
    [maskLinePath stroke];
}

The differences between the first two images are the presence of fill and the specific corner rounding configuration.

2.3 Wave‑Like Curves

Two approaches are presented for generating smooth wave curves from a series of data points:

Method 1 : Build a cubic Bézier curve by calculating control points as the midpoint between consecutive data points.

Method 2 : Use Catmull‑Rom spline interpolation, which guarantees the curve passes through every data point.

Catmull‑Rom formula:

P(t) = 0.5 * (2*p1 + (p2 - p0)*t + (2*p0 - 5*p1 + 4*p2 - p3)*t*t + (3*p1 - p0 - 3*p2 + p3)*t*t*t)

Implementation snippets:

// Bézier‑based wave
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:[self pointAtIndex:0]];
for (NSInteger i = 0; i < count; i++) {
    CGPoint now = [self pointAtIndex:i];
    if (i == 0) { pre = now; }
    else {
        [path addCurveToPoint:now controlPoint1:CGPointMake((pre.x+now.x)/2, pre.y) controlPoint2:CGPointMake((pre.x+now.x)/2, now.y)];
        pre = now;
    }
}
return path;

// Catmull‑Rom smoothing
- (UIBezierPath *)smoothedPathWithGranularity:(NSInteger)granularity path:(UIBezierPath *)path {
    NSMutableArray *pts = [[pointsFromBezierPath(path) mutableCopy] mutableCopy];
    if (pts.count < 4) return [path copy];
    [pts insertObject:pts[0] atIndex:0];
    [pts addObject:pts.lastObject];
    UIBezierPath *smooth = [path copy];
    [smooth removeAllPoints];
    [smooth moveToPoint:POINT(0)];
    for (NSUInteger i = 1; i < pts.count-2; i++) {
        CGPoint p0 = POINT(i-1), p1 = POINT(i), p2 = POINT(i+1), p3 = POINT(i+2);
        for (int j = 1; j < granularity; j++) {
            float t = (float)j / (float)granularity;
            float tt = t*t, ttt = tt*t;
            CGPoint pi;
            pi.x = 0.5 * (2*p1.x + (p2.x-p0.x)*t + (2*p0.x-5*p1.x+4*p2.x-p3.x)*tt + (3*p1.x-p0.x-3*p2.x+p3.x)*ttt);
            pi.y = 0.5 * (2*p1.y + (p2.y-p0.y)*t + (2*p0.y-5*p1.y+4*p2.y-p3.y)*tt + (3*p1.y-p0.y-3*p2.y+p3.y)*ttt);
            if (pi.x <= self.width) [smooth addLineToPoint:pi];
        }
        if (p2.x <= self.width) [smooth addLineToPoint:p2];
    }
    return smooth;
}

Method 1 is easier to understand but requires careful control‑point selection; Method 2 yields smoother curves at the cost of additional complexity.

3 Rendering with CALayer

CALayer is the core graphics object that manages image‑based content and supports implicit animations. Important properties include bounds, position, transform, backgroundColor, cornerRadius, shadow*, and many others.

3.1 CAShapeLayer

CAShapeLayer draws a cubic Bézier spline defined by its path property. Key attributes are fillColor, strokeColor, strokeStart, strokeEnd, lineWidth, lineCap, and lineJoin.

// Example: draw several rectangles and animate the stroke
UIBezierPath *maskPath = [UIBezierPath bezierPath];
for (NSInteger i = 1; i < 9; i++) {
    UIBezierPath *rect = [UIBezierPath bezierPathWithRect:CGRectMake(190-20*i, 550-10*i, 40*i, 20*i)];
    [maskPath appendPath:rect];
}
[maskPath stroke];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.view.bounds;
maskLayer.path = maskPath.CGPath;
maskLayer.lineWidth = 5;
maskLayer.strokeColor = [UIColor purpleColor].CGColor;
maskLayer.fillRule = kCAFillRuleEvenOdd;
maskLayer.fillColor = [UIColor cyanColor].CGColor;
maskLayer.strokeStart = 0.2;
maskLayer.strokeEnd = 0.5;
maskLayer.lineDashPattern = @[@10, @10, @30, @30];
[self.view.layer addSublayer:maskLayer];

3.2 CAGradientLayer

CAGradientLayer renders a color gradient across its bounds. Important properties are colors, locations, startPoint, and endPoint.

CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = CGRectMake(20, 450, 150, 150);
gradient.locations = @[@0.2, @0.5, @0.6, @0.8];
gradient.startPoint = CGPointMake(0, 0);
gradient.endPoint = CGPointMake(1, 1);
gradient.colors = @[(id)[UIColor purpleColor].CGColor, (id)[UIColor greenColor].CGColor, (id)[UIColor orangeColor].CGColor, (id)[UIColor blackColor].CGColor];
[self.view.layer addSublayer:gradient];

3.3 Combined Example

The article concludes with a dynamic demo that combines a custom UIBezierPath, a CAShapeLayer mask, and a CAGradientLayer. A GCD timer updates strokeStart and strokeEnd to animate the drawing while randomly changing gradient colors.

- (void)setupUI {
    // Build Bézier path
    UIBezierPath *maskPath = [UIBezierPath bezierPath];
    [maskPath moveToPoint:CGPointMake(100,220)];
    [maskPath addLineToPoint:CGPointMake(200,150)];
    [maskPath addLineToPoint:CGPointMake(300,220)];
    // Bottom curve
    UIBezierPath *bottom = [UIBezierPath bezierPath];
    [bottom moveToPoint:CGPointMake(280,250)];
    [bottom addCurveToPoint:CGPointMake(120,250) controlPoint1:CGPointMake(250,320) controlPoint2:CGPointMake(150,320)];
    [maskPath appendPath:bottom];
    // Shape layer
    CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
    maskLayer.frame = self.view.bounds;
    maskLayer.path = maskPath.CGPath;
    maskLayer.lineWidth = 20;
    maskLayer.strokeColor = UIColorFromRGB(0xF0F5FF).CGColor;
    maskLayer.lineCap = kCALineCapRound;
    maskLayer.lineJoin = kCALineJoinRound;
    maskLayer.fillColor = [UIColor clearColor].CGColor;
    maskLayer.strokeStart = 0;
    maskLayer.strokeEnd = 0;
    [self.view.layer addSublayer:maskLayer];
    // Gradient layer
    NSMutableArray *colors = [NSMutableArray new];
    for (int i=0;i<6;i++) { [colors addObject:[self arc4randomColor]]; }
    CAGradientLayer *gradient = [CAGradientLayer layer];
    gradient.frame = self.view.bounds;
    gradient.colors = colors;
    gradient.startPoint = CGPointMake(0,0.5);
    gradient.endPoint = CGPointMake(1,0.5);
    gradient.mask = maskLayer;
    [self.view.layer addSublayer:gradient];
    // Timer to animate
    dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0,0,q);
    int64_t interval = (int64_t)(0.3 * NSEC_PER_SEC);
    dispatch_source_set_timer(timer,dispatch_walltime(NULL,0),interval,0);
    dispatch_source_set_event_handler(timer,^{
        dispatch_async(dispatch_get_main_queue(),^{
            if (maskLayer.strokeEnd < 0.6) maskLayer.strokeEnd += 0.4;
            else if (maskLayer.strokeEnd < 0.8) maskLayer.strokeEnd += 0.2;
            else if (maskLayer.strokeEnd < 1) maskLayer.strokeEnd += 0.1;
            else {
                maskLayer.strokeEnd = 1;
                if (maskLayer.strokeStart < 0.6) maskLayer.strokeStart += 0.4;
                else if (maskLayer.strokeStart < 0.8) maskLayer.strokeStart += 0.2;
                else if (maskLayer.strokeStart < 1) maskLayer.strokeStart += 0.1;
                else {
                    [colors removeObjectAtIndex:0];
                    [colors addObject:[self arc4randomColor]];
                    gradient.colors = colors;
                    maskLayer.strokeStart = 0;
                    maskLayer.strokeEnd = 0;
                }
            }
        });
    });
    _timer = timer;
    dispatch_resume(_timer);
}

- (id)arc4randomColor {
    return (id)[UIColor colorWithRed:arc4random()%255/255.f
                               green:arc4random()%255/255.f
                                blue:arc4random()%255/255.f
                               alpha:1].CGColor;
}

This example demonstrates how Bézier paths, shape layers, and gradient layers can be combined to produce rich, animated visual effects on iOS.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

animationGraphicsiOSCALayerBezierUIBezierPath
Beike Product & Technology
Written by

Beike Product & Technology

As Beike's official product and technology account, we are committed to building a platform for sharing Beike's product and technology insights, targeting internet/O2O developers and product professionals. We share high-quality original articles, tech salon events, and recruitment information weekly. Welcome to follow us.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.