iOS
Swift
UIBezierPath
UIView
Drawing

Drawing UIBezierPath on code generated UIView

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Drawing a UIBezierPath in a code-generated UIView works best when rendering code lives in draw(_:) or in a CAShapeLayer managed during layout. The key is understanding which method is for one-time setup versus repeated drawing and keeping geometry calculations tied to view bounds.

Many low-level Q and A style snippets solve the immediate error but skip the engineering context that keeps code reliable over time. A durable solution combines correct syntax with predictable behavior under real inputs, explicit failure handling, and verification that future refactors do not regress the outcome.

When evaluating a fix, also consider maintenance reality: who will own this code in six months, what observability exists in production, and which assumptions are most likely to break first. Capturing intent with small regression tests and clear naming drastically reduces re-learning cost when incidents happen under time pressure.

Core Sections

1. Start with the smallest correct implementation

For custom, immediate drawing, subclass UIView and override draw(_:). Build paths from rect so the drawing stays correct when the view resizes.

swift
1final class BadgeView: UIView {
2    override func draw(_ rect: CGRect) {
3        UIColor.systemBlue.setStroke()
4        let path = UIBezierPath(roundedRect: rect.insetBy(dx: 8, dy: 8),
5                                cornerRadius: 12)
6        path.lineWidth = 3
7        path.stroke()
8    }
9}

This baseline should be intentionally simple. Keep naming precise, make assumptions visible, and avoid premature abstractions. Once the smallest version behaves correctly, you gain a trustworthy reference point for future optimization and architectural changes.

At this stage, add lightweight assertions or logging around critical state transitions. That evidence is invaluable when later optimizations accidentally change behavior, because you can quickly compare current output against the known-good baseline rather than guessing where divergence started.

2. Harden the implementation for real usage

For animated or frequently updated shapes, prefer CAShapeLayer. This avoids redrawing the whole view and lets Core Animation handle interpolation efficiently.

swift
1final class RingView: UIView {
2    private let shape = CAShapeLayer()
3
4    override init(frame: CGRect) {
5        super.init(frame: frame)
6        layer.addSublayer(shape)
7        shape.fillColor = UIColor.clear.cgColor
8        shape.strokeColor = UIColor.systemGreen.cgColor
9        shape.lineWidth = 4
10    }
11
12    required init?(coder: NSCoder) { fatalError() }
13
14    override func layoutSubviews() {
15        super.layoutSubviews()
16        let p = UIBezierPath(ovalIn: bounds.insetBy(dx: 6, dy: 6))
17        shape.path = p.cgPath
18    }
19}

Production hardening is where many bugs are prevented. Address resource management, thread or event-loop safety, edge cases, and consistent error paths. If this logic is part of a service boundary, include clear contracts for inputs, outputs, and failure semantics.

It also helps to separate pure transformation logic from side-effectful operations such as network calls, database writes, or UI mutation. That split makes unit tests faster and deterministic, while integration tests can focus on boundary behavior and failure recovery policies.

3. Verify behavior and performance

Profile rendering with Instruments if scrolling stutters. Avoid allocating large temporary objects per frame and cache immutable paths when possible. Verify line widths and pixel alignment on different screen scales to prevent blurry edges, especially for thin strokes and diagonal lines.

A practical verification loop is straightforward and effective: one happy-path test, one edge-case test, and one failure-path test. Then run with representative data volume or user interactions. If behavior changes after refactoring, keep the regression test so the same issue does not return later.

Performance validation should align with user impact. For APIs, inspect latency percentiles and error rate. For mobile features, monitor frame drops and main-thread stalls. For algorithms and libraries, track complexity growth and memory churn under scaled inputs. Metrics tied to real outcomes keep optimization decisions grounded.

Common Pitfalls

  • Putting drawing logic in init instead of draw(_:) or layoutSubviews.
  • Forgetting to call setNeedsDisplay() when state changes should trigger redraw.
  • Recreating layers repeatedly and leaking visual performance.
  • Using hardcoded coordinates that break on rotation or Auto Layout changes.
  • Ignoring off-main-thread UI updates that cause undefined behavior.

Summary

Use draw(_:) for straightforward custom painting and CAShapeLayer for dynamic or animated shapes. Keep geometry bounds-driven and performance-aware for predictable results. Pair concise implementation with explicit validation, and you get code that is both understandable today and maintainable as requirements evolve.


Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.