setNeedsDisplay(): суть метода и механизм перерисовки в iOS

Автор: IT Sectr Опубликовано: 2026-07-20 Время чтения: 6 мин

setNeedsDisplay() — это метод экземпляра UIView, который помечает вью как требующую повторной отрисовки. В отличие от прямого вызова draw(_:), setNeedsDisplay() лишь устанавливает флаг dirty для вью и возвращает управление немедленно. Система обрабатывает этот запрос в ближайшем цикле рендеринга, что позволяет объединять несколько вызовов в одну операцию отрисовки. По данным Apple Documentation (2025), отложенная перерисовка через setNeedsDisplay() является единственным корректным способом обновления содержимого кастомного UIView.

Главное

  • setNeedsDisplay() — отложенный запрос перерисовки UIView, не вызывает draw(_:) синхронно
  • dirty flag — внутренний флаг вью, который устанавливается setNeedsDisplay() и сбрасывается после draw(_:)
  • setNeedsDisplay(_:) — перегрузка с CGRect для частичной перерисовки только указанной области
  • displayIfNeeded() — принудительная синхронная перерисовка, используется для немедленного обновления
  • Cycle рендеринга — система обрабатывает dirty вью в конце run loop, объединяя несколько запросов в один проход

Что такое setNeedsDisplay()

setNeedsDisplay() — это метод UIView, который сигнализирует системе, что содержимое вью устарело и требует повторного рендеринга. Метод не принимает параметров и возвращает void. После вызова вью помечается как dirty, и в ближайшем цикле отрисовки система вызывает draw(_:) для этого вью.

Ключевая особенность — асинхронность. Несколько подряд вызовов setNeedsDisplay() для одного вью не приведут к множественным вызовам draw(_:) — система объединяет их в одну операцию. Это критически важно для производительности: если свойство вью меняется несколько раз за кадр, draw(_:) вызывается только один раз.

Как работает отложенная перерисовка

Механизм отложенной перерисовки основан на цикле рендеринга UIKit. Когда вызывается setNeedsDisplay(), система устанавливает внутренний флаг layer.needsDisplay в true. В конце текущего run loop система проходит по всем dirty-слоям и вызывает draw(_:) для каждого.

Run loop и шаги рендеринга

UIKit run loop включает фазу update, во время которой обрабатываются dirty-вью. На этой фазе происходит вызов layoutSubviews() при необходимости, затем draw(_:) для всех помеченных вью. После завершения отрисовки результат передаётся на GPU через Core Animation для композитинга.

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)
    }
}

В этом примере progressView обновляет progress, и каждый сеттер автоматически вызывает setNeedsDisplay(). Система дождётся конца run loop и вызовет draw(_:) с актуальным значением progress. Если progress изменится трижды за кадр, draw(_:) выполнится только один раз — с последним значением.

setNeedsDisplay() vs setNeedsDisplay(_:)

Метод setNeedsDisplay(_:) принимает параметр rect типа CGRect, ограничивающий область перерисовки. Это позволяет перерисовывать только изменившуюся часть вью, а не всё целиком. Разница в производительности становится заметной при работе с большими вью, где изменения затрагивают малую область.

ХарактеристикаsetNeedsDisplay()setNeedsDisplay(_:)
ПараметрНетCGRect — область перерисовки
Область перерисовкиВесь bounds вьюТолько указанный прямоугольник
ПроизводительностьНиже при больших вьюВыше при частичных изменениях
Параметр rect в draw(_:)Весь boundsУказанный прямоугольник
Когда использоватьИзменение всего содержимогоТочечные изменения (курсор, highlight)

Для сложной графики рекомендуется сначала рисовать всю сцену в offscreen контекст, а затем вызывать setNeedsDisplay(_:) только для изменившейся области. Это стандартная техника в приложениях для рисования и графических редакторах на iOS.

displayIfNeeded() против setNeedsDisplay()

displayIfNeeded() — это синхронный метод, который немедленно запускает перерисовку всех dirty-вью в иерархии, не дожидаясь конца run loop. В отличие от setNeedsDisplay(), который лишь устанавливает флаг, displayIfNeeded() форсирует немедленное выполнение draw(_:).

Apple рекомендует использовать displayIfNeeded() только в крайних случаях, когда результат отрисовки нужен немедленно — например, перед созданием скриншота или при синхронной генерации изображения для UIGraphicsImageRenderer. В обычных сценариях асинхронная модель через setNeedsDisplay() предпочтительнее, так как не блокирует основной поток.

swift
    // Asynchronous — recommended
someView.setNeedsDisplay()

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

Вызов displayIfNeeded() без предшествующего setNeedsDisplay() не даст эффекта — метод обрабатывает только уже помеченные как dirty вью. Если ни одно вью не требует перерисовки, displayIfNeeded() завершится без вызова draw(_:).

Оптимизация вызовов setNeedsDisplay()

Частые вызовы setNeedsDisplay() могут снизить производительность, особенно при анимации. Рассмотрим основные стратегии оптимизации.

Объединение изменений через модель

Вместо вызова setNeedsDisplay() при каждом изменении отдельного свойства накапливайте изменения и вызывайте метод один раз после применения всех обновлений. Например, при изменении цвета, размера и позиции элемента — вызовите setNeedsDisplay() один раз после установки всех свойств.

Использование CALayer для анимируемых свойств

Если свойство может быть анимировано через CALayer (backgroundColor, opacity, position, transform), используйте Core Animation вместо draw(_:). CALayer-свойства обновляются на GPU без вызова draw(_:), что даёт 60 FPS без нагрузки на CPU.

Пакетное обновление через CATransaction

Для группировки нескольких изменений в один проход рендеринга используйте CATransaction. Это позволяет объединить несколько setNeedsDisplay() в одну операцию draw(_:) в конце транзакции.

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

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

CATransaction.commit()

Не злоупотребляйте displayIfNeeded() — синхронная перерисовка блокирует main thread и может вызвать дроп кадров. Используйте её только для операций, где результат отрисовки критически важен до завершения текущего run loop.

Часто задаваемые вопросы

Можно ли вызвать draw(_:) напрямую вместо setNeedsDisplay()?

Нет, прямой вызов draw(_:) запрещён. setNeedsDisplay() корректно помечает вью как dirty, и система вызывает draw(_:) в нужный момент цикла рендеринга. Прямой вызов draw(_:) игнорирует механизм кэширования и может привести к неконсистентному состоянию.

Что произойдёт при множественных вызовах setNeedsDisplay()?

Несколько вызовов setNeedsDisplay() для одного вью объединяются системой в один вызов draw(_:). Это происходит потому, что метод лишь устанавливает dirty flag, который сбрасывается только после реального выполнения draw(_:) в конце run loop.

Чем setNeedsDisplay() отличается от setNeedsLayout()?

setNeedsDisplay() запрашивает перерисовку содержимого вью (вызов draw(_:)). setNeedsLayout() запрашивает перекомпоновку сабвью (вызов layoutSubviews()). layoutSubviews() может привести к изменению размеров, что в свою очередь может вызвать перерисовку.

Когда использовать setNeedsDisplay(_:)?

setNeedsDisplay(_:) следует использовать при точечных изменениях: движение курсора, изменение цвета выделенной области, обновление части графика. Это ограничивает область перерисовки и повышает производительность по сравнению с полной перерисовкой всего вью.

Вызывает ли изменение frame setNeedsDisplay() автоматически?

По умолчанию изменение frame или bounds не вызывает setNeedsDisplay(). Система лишь перемещает вью. Если требуется перерисовка при изменении размеров, установите свойству contentMode значение .redraw — тогда UIKit будет автоматически вызывать setNeedsDisplay() при каждом изменении bounds.

Итоги

  • setNeedsDisplay() — асинхронный метод UIView для запроса отложенной перерисовки через установку dirty flag
  • setNeedsDisplay(_:) — перегрузка с CGRect для частичной перерисовки только указанной области, оптимизирует производительность
  • displayIfNeeded() — синхронный метод для немедленной перерисовки dirty-вью, используется только при крайней необходимости
  • Run loop — система обрабатывает dirty-вью в конце текущего цикла, объединяя множественные вызовы в один проход draw(_:)
  • CALayer — для анимируемых свойств (position, opacity) используйте Core Animation вместо draw(_:) — это на порядок производительнее
  • CATransaction — группирует несколько изменений в один цикл рендеринга, снижая количество вызовов draw(_:)
  • contentMode = .redraw — включает автоматический вызов setNeedsDisplay() при изменении bounds вью

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также