setNeedsDisplay(): method essence and redrawing mechanism in iOS

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

setNeedsDisplay() is an instance method of UIView that marks the view as needing to be redrawn. Unlike directly calling draw(_:), setNeedsDisplay() only sets the dirty flag for the view and returns control immediately. The system processes this request in the nearest rendering cycle, allowing multiple calls to be combined into a single draw operation. According to Apple Documentation (2025), deferred redrawing through setNeedsDisplay() is the only correct way to update the content of a custom UIView.

Key Takeaways

  • setNeedsDisplay() — a deferred redraw request for UIView, does not call draw(_:) synchronously
  • dirty flag — an internal view flag set by setNeedsDisplay() and cleared after draw(_:)
  • setNeedsDisplay(_:) — an overload with CGRect for partial redrawing of only the specified region
  • displayIfNeeded() — forced synchronous redrawing, used for immediate updates
  • Rendering cycle — the system processes dirty views at the end of the run loop, combining multiple requests into one pass

What is setNeedsDisplay()

setNeedsDisplay() is a UIView method that signals to the system that the view's content is outdated and needs re-rendering. The method takes no parameters and returns void. After the call, the view is marked as dirty, and in the nearest drawing cycle the system calls draw(_:) for this view.

The key feature is asynchronicity. Multiple consecutive calls to setNeedsDisplay() for the same view will not result in multiple draw(_:) calls — the system combines them into a single operation. This is critically important for performance: if a view property changes multiple times per frame, draw(_:) is called only once.

How deferred redrawing works

The deferred redrawing mechanism is based on the UIKit rendering cycle. When setNeedsDisplay() is called, the system sets the internal flag layer.needsDisplay to true. At the end of the current run loop, the system iterates through all dirty layers and calls draw(_:) for each one.

Run loop and rendering steps

The UIKit run loop includes an update phase during which dirty views are processed. In this phase, layoutSubviews() is called if needed, then draw(_:) for all marked views. After drawing is complete, the result is passed to the GPU through Core Animation for compositing.

swift
class ProgressView: UIView {
    var progress: CGFloat = 0 {
        didSet {
            setNeedsDisplay()
        }
    }

    override func draw(_ rect: CGRect) {
        super.draw(rect)
        guard let ctx = UIGraphicsGetCurrentContext() else { return }

        // Background
        ctx.setFillColor(UIColor.lightGray.cgColor)
        ctx.fill(rect)

        // Progress bar
        let fillRect = CGRect(x: rect.minX, y: rect.minY,
                              width: rect.width * progress, height: rect.height)
        ctx.setFillColor(UIColor.systemGreen.cgColor)
        ctx.fill(fillRect)
    }
}

In this example, progressView updates progress, and each setter automatically calls setNeedsDisplay(). The system waits for the end of the run loop and calls draw(_:) with the actual progress value. If progress changes three times per frame, draw(_:) executes only once — with the last value.

setNeedsDisplay() vs setNeedsDisplay(_:)

The setNeedsDisplay(_:) method takes a rect parameter of type CGRect, limiting the redraw area. This allows redrawing only the changed part of the view, rather than the entire view. The performance difference becomes noticeable when working with large views where changes affect a small area.

CharacteristicsetNeedsDisplay()setNeedsDisplay(_:)
ParameterNoneCGRect — redraw area
Redraw areaEntire view boundsOnly the specified rectangle
PerformanceLower for large viewsHigher for partial changes
rect parameter in draw(_:)Entire boundsSpecified rectangle
When to useChanging entire contentPoint changes (cursor, highlight)

For complex graphics, it is recommended to draw the entire scene in an offscreen context first, and then call setNeedsDisplay(_:) only for the changed area. This is a standard technique in drawing applications and graphic editors on iOS.

displayIfNeeded() vs setNeedsDisplay()

displayIfNeeded() is a synchronous method that immediately triggers redrawing of all dirty views in the hierarchy, without waiting for the end of the run loop. Unlike setNeedsDisplay(), which only sets a flag, displayIfNeeded() forces immediate execution of draw(_:).

Apple recommends using displayIfNeeded() only in extreme cases when the drawing result is needed immediately — for example, before taking a screenshot or during synchronous image generation for UIGraphicsImageRenderer. In normal scenarios, the asynchronous model through setNeedsDisplay() is preferable as it does not block the main thread.

swift
    // Asynchronous — recommended
someView.setNeedsDisplay()

    // Synchronous — only when necessary
someView.setNeedsDisplay()
someView.displayIfNeeded() // draw(_:) called immediately

Calling displayIfNeeded() without a preceding setNeedsDisplay() will have no effect — the method only processes views already marked as dirty. If no view needs redrawing, displayIfNeeded() completes without calling draw(_:).

Optimizing setNeedsDisplay() calls

Frequent calls to setNeedsDisplay() can degrade performance, especially during animation. Let's look at the main optimization strategies.

Batching changes through a model

Instead of calling setNeedsDisplay() on every change of an individual property, accumulate changes and call the method once after applying all updates. For example, when changing the color, size, and position of an element — call setNeedsDisplay() once after setting all properties.

Using CALayer for animatable properties

If a property can be animated through CALayer (backgroundColor, opacity, position, transform), use Core Animation instead of draw(_:). CALayer properties update on the GPU without calling draw(_:), providing 60 FPS with no CPU load.

Batch update through CATransaction

To group multiple changes into a single rendering pass, use CATransaction. This allows combining multiple setNeedsDisplay() calls into one draw(_:) operation at the end of the transaction.

swift
CATransaction.begin()
CATransaction.setDisableActions(true)

view1.setNeedsDisplay()
view2.setNeedsDisplay()
view3.setNeedsDisplay()

CATransaction.commit()

Do not overuse displayIfNeeded() — synchronous redrawing blocks the main thread and can cause frame drops. Use it only for operations where the drawing result is critical before the current run loop ends.

Frequently Asked Questions

Can I call draw(_:) directly instead of setNeedsDisplay()?

No, directly calling draw(_:) is prohibited. setNeedsDisplay() correctly marks the view as dirty, and the system calls draw(_:) at the right moment in the rendering cycle. Directly calling draw(_:) ignores the caching mechanism and can lead to an inconsistent state.

What happens with multiple calls to setNeedsDisplay()?

Multiple calls to setNeedsDisplay() for the same view are combined by the system into a single draw(_:) call. This happens because the method only sets the dirty flag, which is only cleared after the actual execution of draw(_:) at the end of the run loop.

How is setNeedsDisplay() different from setNeedsLayout()?

setNeedsDisplay() requests a redraw of the view's content (calling draw(_:)). setNeedsLayout() requests a re-layout of subviews (calling layoutSubviews()). layoutSubviews() can lead to size changes, which in turn may trigger a redraw.

When to use setNeedsDisplay(_:)?

setNeedsDisplay(_:) should be used for point changes: cursor movement, color change of a selected area, updating part of a graph. This limits the redraw area and improves performance compared to redrawing the entire view.

Does changing frame automatically call setNeedsDisplay()?

By default, changing frame or bounds does not call setNeedsDisplay(). The system only moves the view. If redrawing is required when the size changes, set the contentMode property to .redraw — then UIKit will automatically call setNeedsDisplay() on every bounds change.

Summary

  • setNeedsDisplay() — an asynchronous UIView method for requesting deferred redrawing by setting the dirty flag
  • setNeedsDisplay(_:) — an overload with CGRect for partial redrawing of only the specified region, optimizing performance
  • displayIfNeeded() — a synchronous method for immediate redrawing of dirty views, used only when absolutely necessary
  • Run loop — the system processes dirty views at the end of the current cycle, combining multiple calls into a single draw(_:) pass
  • CALayer — for animatable properties (position, opacity) use Core Animation instead of draw(_:) — it is an order of magnitude more performant
  • CATransaction — groups multiple changes into one rendering cycle, reducing the number of draw(_:) calls
  • contentMode = .redraw — enables automatic setNeedsDisplay() call when the view's bounds change

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