UIBezierPath: What It Is, Drawing Methods, and How It Works

Author: IT Sectr Published: 2026-07-21 Reading time: 8 min

UIBezierPath is a UIKit class that encapsulates a vector path for 2D graphics in iOS. It combines geometric path description with drawing capabilities, allowing you to create lines, Bezier curves, arcs, and rectangles. Unlike CGPath, UIBezierPath contains its own drawing methods that work directly in the current graphics context. According to Apple Developer Documentation, 2026, UIBezierPath supports all standard Quartz 2D operations in an object-oriented wrapper.

Key Takeaways

  • UIBezierPath is an object-oriented wrapper for CGPath used for working with vector paths in UIKit.
  • move(to:) and addLine(to:) define straight path segments.
  • addQuadCurve(to:controlPoint:) and addCurve(to:controlPoint1:controlPoint2:) construct Bezier curves.
  • addArc(withCenter:radius:startAngle:endAngle:clockwise:) creates arcs and sectors.
  • fill and stroke — direct path drawing in the current context without using additional objects.

What Is UIBezierPath?

UIBezierPath is a UIKit framework class that inherits from NSObject and implements NSCopying, NSSecureCoding. It provides a high-level Objective-C/Swift API for creating and drawing vector paths. Internally, UIBezierPath stores a reference to CGPathRef — a Core Graphics object — but adds convenient methods for drawing, geometry modification, and style management.

The main advantage of UIBezierPath over using CGPath directly is the built-in drawing methods. Calling fill() or stroke() on a UIBezierPath instance automatically configures the context and draws the path. The class also supports lineWidth, lineCapStyle, lineJoinStyle, miterLimit, and flatness properties, allowing you to control appearance without a separate style storage object.

UIBezierPath is used in most iOS applications for custom drawing inside UIView: from simple icons to complex animated charts. The draw(_:) method of the UIView class is the primary place where developers create and draw UIBezierPath. The class is fully compatible with Core Animation and can be used for path animation via CAShapeLayer and CABasicAnimation.

Straight Lines and Rectangles

The move(to:) method moves the path starting point to the specified coordinates without drawing. This is a required call before building any segment. addLine(to:) adds a straight segment from the current position to the specified point. The close() method closes the contour by drawing a line from the last point back to the first point of the last move(to:).

Rounded Rectangles

UIBezierPath provides several convenient initializers for ready-made shapes. init(rect:) creates a rectangle, init(roundedRect:cornerRadius:) creates a rectangle with rounded corners. The UIRectCorner parameter allows rounding only selected corners, such as topLeft and bottomRight, leaving the others straight. This is widely used for creating custom buttons and cards.

init(ovalIn:) creates an ellipse inscribed in the specified rectangle. If the rectangle is a square, the ellipse becomes a circle. init(roundedRect:byRoundingCorners:cornerRadii:) is the most flexible option, allowing different radii for each corner. This initializer was introduced in iOS 6 and remains the standard for creating rounded containers.

swift
let path = UIBezierPath()
path.move(to: CGPoint(x: 50, y: 50))
path.addLine(to: CGPoint(x: 200, y: 50))
path.addLine(to: CGPoint(x: 200, y: 150))
path.addLine(to: CGPoint(x: 125, y: 200))
path.addLine(to: CGPoint(x: 50, y: 150))
path.close()

Bezier Curves: Quadratic and Cubic

addQuadCurve(to:controlPoint:) adds a quadratic Bezier curve with one control point. This type of curve is used for simple smooth bends when minimizing computation. A quadratic curve is always a parabola and is guaranteed to lie within the triangle formed by the start, control, and end points.

Cubic Bezier Curves

addCurve(to:controlPoint1:controlPoint2:) adds a cubic Bezier curve with two control points. Cubic curves are used in all modern vector editors and TrueType fonts. Two control points provide more degrees of freedom for precise shape control. This method is recommended for building organic and complex contours.

When constructing curves, it is important to maintain the continuity principle — smoothness at the connection point of segments. For C1 continuity (tangent), the control point of the next curve must lie on the same line as the control point of the previous one. For C2 continuity (curvature), more complex coordination is required, which is automatically provided in CAShapeLayer when animating between two UIBezierPath objects with the same number of control points.

MethodTypeControl PointsDegree
addQuadCurveQuadratic Bezier12
addCurveCubic Bezier23
addArcArc

Arcs and Circles

addArc(withCenter:radius:startAngle:endAngle:clockwise:) adds a circular arc with the specified parameters. Angles are specified in radians relative to the circle center. Zero angle corresponds to the rightward direction (3 o'clock on a clock face). The clockwise parameter determines the direction: true for clockwise, false for counterclockwise. Arcs are essential for pie charts, speedometers, and interactive progress rings.

Circles and Sectors

For a full circle, use startAngle = 0 and endAngle = .pi * 2. To create a sector (as part of a pie chart), use addArc followed by addLine(to:) to the center and close(). UIBezierPath does not have a built-in addCircle method — a circle is created via init(ovalIn:) with a square rectangle or via addArc with a full angle.

The addArc(withCenter:radius:startAngle:endAngle:clockwise:) method automatically performs move(to:) to the arc starting point if the current position does not match it. If the current position differs, a straight line is drawn between it and the start of the arc. This behavior differs from Core Graphics, where an explicit move(to:) is required before each new contour.

swift
let center = CGPoint(x: 150, y: 150)
let slice = UIBezierPath()
slice.move(to: center)
slice.addArc(
    withCenter: center,
    radius: 100,
    startAngle: 0,
    endAngle: .pi * 0.75,
    clockwise: true
)
slice.close()

Fill and Stroke of the Path

fill() fills the interior of the path with the current fillColor or the color set in the graphics context via setFill(). For self-intersecting paths, the fill rule is determined by the usesEvenOddFillRule property. If set to true, the even-odd rule is used: a point is considered inside the shape if a ray from it crosses the contour an odd number of times.

Stroke Settings

stroke() draws the path contour with the current lineWidth, lineCapStyle, and lineJoinStyle settings. lineCapStyle defines the shape of open segment ends: .butt (straight cut), .round (semicircular), .square (square with projection). lineJoinStyle defines the shape of joint corners: .miter (sharp), .round (rounded), .bevel (cut off).

For dashed lines, the setLineDash(_:count:phase:) property is used. It accepts an array of alternating dash and gap lengths, the count of elements, and the initial phase. Dash rendering is performed during path rasterization — complex patterns do not increase the geometric complexity of the contour. CAShapeLayer supports animation of the strokeEnd property for a “drawing” effect from start to finish.

swift
override func draw(_ rect: CGRect) {
    let path = UIBezierPath(ovalIn: CGRect(x: 50, y: 50, width: 200, height: 200))
    UIColor.blue.setFill()
    UIColor.black.setStroke()
    path.lineWidth = 4
    path.fill()
    path.stroke()
}

Example of Creating a Complex Shape

Let's create a custom progress indicator in the form of a ring using UIBezierPath. The ring is built from two arcs of the same radius with different stroke colors. The first arc is a full circle in gray (background), the second is a segment showing progress. For animation, CAShapeLayer and CABasicAnimation are used.

swift
class RingView: UIView {

    private let progressLayer = CAShapeLayer()

    override func layoutSubviews() {
        super.layoutSubviews()
        let center = CGPoint(x: bounds.midX, y: bounds.midY)
        let radius = bounds.width * 0.4
        let lineWidth: CGFloat = 12

        let backgroundRing = UIBezierPath()
        backgroundRing.addArc(withCenter: center, radius: radius,
                          startAngle: -.pi / 2, endAngle: .pi * 1.5, clockwise: true)

        progressLayer.path = backgroundRing.cgPath
        progressLayer.strokeColor = UIColor.systemBlue.cgColor
        progressLayer.lineWidth = lineWidth
        progressLayer.lineCap = .round
        progressLayer.fillColor = UIColor.clear.cgColor
        progressLayer.strokeEnd = 0
        layer.addSublayer(progressLayer)
    }

    func animateProgress(to value: CGFloat) {
        CABasicAnimation(keyPath: "strokeEnd").apply {
            $0.toValue = value
            $0.duration = 1.5
            $0.timingFunction = CAMediaTimingFunction(name: .easeOut)
            progressLayer.add($0, forKey: nil)
        }
        progressLayer.strokeEnd = value
    }
}

In this example, UIBezierPath is used to create a circular contour, which is then passed to CAShapeLayer via the cgPath property. Animating strokeEnd from 0 to 1 visualizes the ring filling. This approach is used in loading indicators, fitness trackers, and progress widgets. The layoutSubviews method ensures path reconstruction when the View size changes.

lineCap = .round adds rounded ends to the arc, giving the indicator a neater look. The strokeEnd property is an animatable property of CAShapeLayer that does not require redrawing the entire contour each frame. Animation runs entirely on the GPU through Core Animation, ensuring 60 FPS with no CPU load.

Frequently Asked Questions

How is UIBezierPath different from CGPath?

UIBezierPath is an object-oriented wrapper around CGPath that includes built-in fill() and stroke() methods. CGPath is an immutable C object from Core Graphics that requires explicit drawing via CGContext. UIBezierPath is more convenient for UIKit, while CGPath is more performant for reuse.

How do I round only the top corners of a rectangle?

Use init(roundedRect:byRoundingCorners:cornerRadii:) with the .topLeft and .topRight parameters for roundedRect. This is the only UIBezierPath method that allows selectively rounding corners without creating a complex composite path.

How do I reuse UIBezierPath with different styles?

The cgPath property returns an immutable CGPathRef that can be passed to CAShapeLayer or used with different colors and thicknesses. Modifications to UIBezierPath (point movement) do not affect the previously obtained cgPath until explicitly updated.

Why doesn't fill() cover the entire path?

Check usesEvenOddFillRule — if the contour has self-intersections, the even-odd rule may leave areas unfilled. Set usesEvenOddFillRule = false for the standard non-zero winding fill rule.

How do I animate a UIBezierPath shape?

Use CABasicAnimation with the key path "path" on CAShapeLayer. Both path states must contain the same number of segments and control points — otherwise the animation will be jerky or fail.

Summary

  • UIBezierPath is an object-oriented UIKit class for working with vector paths and 2D graphics.
  • move(to:) and addLine(to:) are basic methods for building straight lines and polygons.
  • addQuadCurve and addCurve create quadratic and cubic Bezier curves with one and two control points.
  • addArc draws circular arcs with flexible angle and direction control.
  • fill() and stroke() perform drawing directly in the current graphics context.
  • Initializers init(roundedRect:) and init(ovalIn:) speed up the creation of standard shapes.
  • The cgPath property allows integrating UIBezierPath with Core Animation and CAShapeLayer for animation.

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also