draw(_:) and drawRect — what it is, calling and overriding

Author: IT Sectr Published: 2026-07-20 Reading time: 7 min

draw(_:) / drawRect is a method of the UIView class (in Swift) and its counterpart in Objective-C (drawRect:), which is responsible for rendering the view's content using Core Graphics. The system calls this method automatically when the view first appears on screen and after calling setNeedsDisplay(). According to Apple Documentation (2025), draw(_:) is the only method where the developer has access to the current screen's graphics context for custom drawing. Overriding draw(_:) gives full control over the component's appearance — from simple geometric shapes to complex animated graphics.

Key Takeaways

  • draw(_:) — UIView method for custom drawing via Core Graphics, not called directly by the developer
  • drawRect: — equivalent of draw(_:) in Objective-C, syntactically different, functionally identical
  • CGContext — graphics context available inside draw(_:) for all drawing operations
  • setNeedsDisplay() — the only correct way to request a redraw of draw(_:) from the developer side
  • UIGraphicsGetCurrentContext() — function to get the current context inside draw(_:), a mandatory step before drawing

What is draw(_:) / drawRect

draw(_:) is an instance method of UIView that UIKit calls to render the view's content. Inside this method, the developer gets access to the CGContext graphics context and uses the Core Graphics API to draw lines, fills, text, and images. drawRect: in Objective-C performs the same function but with different syntax: the only parameter is a CGRect specifying the area to redraw.

According to Apple Engineering (2024), draw(_:) works through CPU-based Bitmap Graphics Context rendering, which provides maximum flexibility but requires more resources compared to CALayer. The decision to use draw(_:) is made based on the complexity of the graphics and performance requirements.

Method signature in Swift and Objective-C

In Swift, the method is declared as override func draw(_ rect: CGRect), where rect is the rectangle that needs to be redrawn. In Objective-C, the signature is - (void)drawRect:(CGRect)rect. The rect parameter may be smaller than the view's bounds during partial redrawing via setNeedsDisplay(_:).

objective-c
- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
    CGContextFillRect(context, rect);
}

When the system calls draw(_:)

The system automatically calls draw(_:) in strictly defined scenarios. Understanding these triggers helps avoid unnecessary redraws and optimize view performance. Below are all cases of automatic method invocation.

  • First render — when the view is first added to the hierarchy and becomes visible on screen
  • setNeedsDisplay() — after calling this method, the system redraws during the next drawing cycle
  • setNeedsDisplay(_:) — same as above, but specifying a specific rectangle for redrawing
  • contentMode — when bounds change, if contentMode requires redrawing (e.g., .redraw)
  • setNeedsLayout() — in some cases, after subview re-layout, redrawing may be needed

Apple documentation warns: never call draw(_:) directly. The system itself decides when to perform drawing, and a direct call disrupts the internal caching mechanism. To request a redraw, always use setNeedsDisplay() or setNeedsDisplay(_:).

How to override draw(_:) in Swift

Overriding draw(_:) in Swift starts with obtaining the graphics context and subsequent calls to Core Graphics. It is recommended to create separate methods for logical drawing blocks — this improves code readability and testability.

swift
override func draw(_ rect: CGRect) {
    super.draw(rect)

    guard let context = UIGraphicsGetCurrentContext() else { return }

    // Line parameters
    context.setStrokeColor(UIColor.darkGray.cgColor)
    context.setLineWidth(2.0)

    // Drawing a triangle
    context.move(to: CGPoint(x: rect.midX, y: rect.minY + 10))
    context.addLine(to: CGPoint(x: rect.maxX - 10, y: rect.maxY - 10))
    context.addLine(to: CGPoint(x: rect.minX + 10, y: rect.maxY - 10))
    context.closePath()
    context.strokePath()
}

In this example, draw(_:) draws a triangle with a dark gray outline 2 pixels thick. Calling super.draw(rect) at the beginning of the method is recommended by Apple to preserve the parent drawing logic, although UIView's default draw(_:) implementation is empty.

The idempotency rule of draw(_:)

Each call to draw(_:) should produce an identical result given the same input data. This allows the system to cache the result and not call draw(_:) again if the view's content hasn't changed. Do not use random values, system time, or network requests inside draw(_:).

drawRect: in Objective-C and differences from draw(_:)

drawRect: is historically the first version of the method, appearing in iOS 2.0 with Objective-C. In Swift, the method was renamed to draw(_:) using the external parameter _. Functionally, the methods are identical: both receive a CGRect of the area to redraw and use UIGraphicsGetCurrentContext() to access the graphics context.

Characteristicdraw(_:) (Swift)drawRect: (Objective-C)
Signatureoverride func draw(_ rect: CGRect)- (void)drawRect:(CGRect)rect
Calling supersuper.draw(rect)[super drawRect:rect]
ContextUIGraphicsGetCurrentContext()UIGraphicsGetCurrentContext()
rect parameterrect: CGRectCGRect rect
PerformanceIdenticalIdentical

When migrating a project from Objective-C to Swift, renaming the method is one of the first tasks. Xcode provides an automatic converter, but drawRect: requires manual updating to draw(_:). According to Apple (2024), the Swift version draw(_:) is preferred for new projects.

Optimizing drawing in draw(_:)

draw(_:) runs on the CPU, and a suboptimal implementation can cause frame drops and poor performance. Apple recommends several proven approaches to speed up drawing.

Minimize the number of drawing operations

Each Core Graphics operation (move(to:), addLine(to:), strokePath) has overhead. Group operations and use CGPath for complex shapes — the path is created once and reused on every draw(_:) call.

Use UIBezierPath for vector objects

UIBezierPath is an Objective-C wrapper over CGPath that provides a simple API for creating shapes. Create UIBezierPath in advance (e.g., in the initializer) and simply call fill() or stroke() inside draw(_:).

swift
private let starPath: UIBezierPath = {
    let path = UIBezierPath()
    // Building a star shape
    path.move(to: CGPoint(x: 50, y: 0))
    for i in 1...5 {
        let angle = CGFloat(i) * 4 * CGFloat.pi / 5
        path.addLine(to: CGPoint(x: 50 + 40 * cos(angle),
                               y: 50 + 40 * sin(angle)))
    }
    path.close()
    return path
}()

override func draw(_ rect: CGRect) {
    UIColor.systemYellow.setFill()
    starPath.fill()
}

Common mistakes when working with draw(_:)

Developers often make the same mistakes when overriding draw(_:). Knowing these patterns helps avoid bugs and performance degradation. Let's look at the most common problems and their solutions.

  • Direct call to draw(_:) — never call draw(_:) directly. Use setNeedsDisplay() to request a redraw. Direct calling breaks caching and can lead to incorrect display.
  • Heavy computations inside draw(_:) — draw(_:) should be as lightweight as possible. Create UIBezierPath, images, and other heavy objects outside the method, initializing them once.
  • Creating objects inside draw(_:) — UIColor, UIFont, and UIGraphicsImageRenderer constructions inside draw(_:) create unnecessary memory load. Move object creation to class properties.
  • Ignoring the rect parameter — rect specifies the area that needs redrawing. Drawing outside rect is discarded by the system but wastes resources. Check intersection with rect before drawing.
  • Missing super.draw(rect) — although UIView's implementation is empty, Apple recommends calling super.draw(rect) for compatibility with future UIKit changes.

By following these rules, you will ensure stable and fast custom view drawing in any iOS project. Profile draw(_:) through Instruments (Core Animation) to see actual execution time and bottlenecks.

Frequently Asked Questions

Can I call draw(_:) directly?

No, directly calling draw(_:) is prohibited by Apple documentation. The system manages the drawing cycle itself. To request a redraw, use setNeedsDisplay(), which correctly marks the view as needing an update in the nearest rendering cycle.

How is drawRect: different from draw(_:)?

Functionally these methods are identical. drawRect: is used in Objective-C, draw(_:) in Swift. Both receive a CGRect of the area to redraw and use the same Core Graphics context via UIGraphicsGetCurrentContext().

Why is draw(_:) not called for an empty UIView?

Apple optimizes rendering: if a UIView has no overridden draw(_:), the system does not create a bitmap context for it. This saves memory. If the override exists but the method is not called — check that the view's frame is non-zero and the view is visible.

How often does the system call draw(_:)?

Only when needed: first render, after setNeedsDisplay(), when bounds change with contentMode = .redraw. In a static state, draw(_:) is not called again, saving CPU and battery resources.

Do I need to call super.draw(rect) in Swift?

Apple recommends calling super.draw(rect) at the beginning of the overridden method. Although the current UIView implementation is empty, the super call ensures compatibility with future UIKit versions and is good practice.

Summary

  • draw(_:) / drawRect — the main UIView drawing method in Swift and Objective-C, called automatically by the system
  • CGContext — Core Graphics context available inside draw(_:) via UIGraphicsGetCurrentContext()
  • setNeedsDisplay() — the correct way to request a redraw; direct calling of draw(_:) is prohibited
  • Idempotency — draw(_:) should produce the same result with the same input data for proper caching
  • UIBezierPath — create paths outside draw(_:) to minimize CPU load on each call
  • rect parameter — contains the redraw area; use intersection for optimization — do not draw outside it
  • Profiling — check draw(_:) performance via Instruments Core Animation to identify slow operations

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