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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
