setNeedsDisplay() — это метод экземпляра UIView, который помечает вью как требующую повторной отрисовки. В отличие от прямого вызова draw(_:), setNeedsDisplay() лишь устанавливает флаг dirty для вью и возвращает управление немедленно. Система обрабатывает этот запрос в ближайшем цикле рендеринга, что позволяет объединять несколько вызовов в одну операцию отрисовки. По данным Apple Documentation (2025), отложенная перерисовка через setNeedsDisplay() является единственным корректным способом обновления содержимого кастомного UIView.
Главное
setNeedsDisplay() — это метод UIView, который сигнализирует системе, что содержимое вью устарело и требует повторного рендеринга. Метод не принимает параметров и возвращает void. После вызова вью помечается как dirty, и в ближайшем цикле отрисовки система вызывает draw(_:) для этого вью.
Ключевая особенность — асинхронность. Несколько подряд вызовов setNeedsDisplay() для одного вью не приведут к множественным вызовам draw(_:) — система объединяет их в одну операцию. Это критически важно для производительности: если свойство вью меняется несколько раз за кадр, draw(_:) вызывается только один раз.
Механизм отложенной перерисовки основан на цикле рендеринга UIKit. Когда вызывается setNeedsDisplay(), система устанавливает внутренний флаг layer.needsDisplay в true. В конце текущего run loop система проходит по всем dirty-слоям и вызывает draw(_:) для каждого.
UIKit run loop включает фазу update, во время которой обрабатываются dirty-вью. На этой фазе происходит вызов layoutSubviews() при необходимости, затем draw(_:) для всех помеченных вью. После завершения отрисовки результат передаётся на GPU через Core Animation для композитинга.
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(_:) принимает параметр rect типа CGRect, ограничивающий область перерисовки. Это позволяет перерисовывать только изменившуюся часть вью, а не всё целиком. Разница в производительности становится заметной при работе с большими вью, где изменения затрагивают малую область.
| Характеристика | setNeedsDisplay() | setNeedsDisplay(_:) |
|---|---|---|
| Параметр | Нет | CGRect — область перерисовки |
| Область перерисовки | Весь bounds вью | Только указанный прямоугольник |
| Производительность | Ниже при больших вью | Выше при частичных изменениях |
| Параметр rect в draw(_:) | Весь bounds | Указанный прямоугольник |
| Когда использовать | Изменение всего содержимого | Точечные изменения (курсор, highlight) |
Для сложной графики рекомендуется сначала рисовать всю сцену в offscreen контекст, а затем вызывать setNeedsDisplay(_:) только для изменившейся области. Это стандартная техника в приложениях для рисования и графических редакторах на iOS.
displayIfNeeded() — это синхронный метод, который немедленно запускает перерисовку всех dirty-вью в иерархии, не дожидаясь конца run loop. В отличие от setNeedsDisplay(), который лишь устанавливает флаг, displayIfNeeded() форсирует немедленное выполнение draw(_:).
Apple рекомендует использовать displayIfNeeded() только в крайних случаях, когда результат отрисовки нужен немедленно — например, перед созданием скриншота или при синхронной генерации изображения для UIGraphicsImageRenderer. В обычных сценариях асинхронная модель через setNeedsDisplay() предпочтительнее, так как не блокирует основной поток.
// Asynchronous — recommended
someView.setNeedsDisplay()
// Synchronous — only when necessary
someView.setNeedsDisplay()
someView.displayIfNeeded() // draw(_:) called immediately
Вызов displayIfNeeded() без предшествующего setNeedsDisplay() не даст эффекта — метод обрабатывает только уже помеченные как dirty вью. Если ни одно вью не требует перерисовки, displayIfNeeded() завершится без вызова draw(_:).
Частые вызовы setNeedsDisplay() могут снизить производительность, особенно при анимации. Рассмотрим основные стратегии оптимизации.
Вместо вызова setNeedsDisplay() при каждом изменении отдельного свойства накапливайте изменения и вызывайте метод один раз после применения всех обновлений. Например, при изменении цвета, размера и позиции элемента — вызовите setNeedsDisplay() один раз после установки всех свойств.
Если свойство может быть анимировано через CALayer (backgroundColor, opacity, position, transform), используйте Core Animation вместо draw(_:). CALayer-свойства обновляются на GPU без вызова draw(_:), что даёт 60 FPS без нагрузки на CPU.
Для группировки нескольких изменений в один проход рендеринга используйте CATransaction. Это позволяет объединить несколько setNeedsDisplay() в одну операцию draw(_:) в конце транзакции.
CATransaction.begin()
CATransaction.setDisableActions(true)
view1.setNeedsDisplay()
view2.setNeedsDisplay()
view3.setNeedsDisplay()
CATransaction.commit()
Не злоупотребляйте displayIfNeeded() — синхронная перерисовка блокирует main thread и может вызвать дроп кадров. Используйте её только для операций, где результат отрисовки критически важен до завершения текущего run loop.
Часто задаваемые вопросы
Нет, прямой вызов draw(_:) запрещён. setNeedsDisplay() корректно помечает вью как dirty, и система вызывает draw(_:) в нужный момент цикла рендеринга. Прямой вызов draw(_:) игнорирует механизм кэширования и может привести к неконсистентному состоянию.
Несколько вызовов setNeedsDisplay() для одного вью объединяются системой в один вызов draw(_:). Это происходит потому, что метод лишь устанавливает dirty flag, который сбрасывается только после реального выполнения draw(_:) в конце run loop.
setNeedsDisplay() запрашивает перерисовку содержимого вью (вызов draw(_:)). setNeedsLayout() запрашивает перекомпоновку сабвью (вызов layoutSubviews()). layoutSubviews() может привести к изменению размеров, что в свою очередь может вызвать перерисовку.
setNeedsDisplay(_:) следует использовать при точечных изменениях: движение курсора, изменение цвета выделенной области, обновление части графика. Это ограничивает область перерисовки и повышает производительность по сравнению с полной перерисовкой всего вью.
По умолчанию изменение frame или bounds не вызывает setNeedsDisplay(). Система лишь перемещает вью. Если требуется перерисовка при изменении размеров, установите свойству contentMode значение .redraw — тогда UIKit будет автоматически вызывать setNeedsDisplay() при каждом изменении bounds.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также