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 включає фазу оновлення, під час якої обробляються 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 }
// Фон
ctx.setFillColor(UIColor.lightGray.cgColor)
ctx.fill(rect)
// Панель прогресу
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() є кращою, оскільки не блокує головний потік.
// Асинхронний — рекомендовано
someView.setNeedsDisplay()
// Синхронний — лише коли необхідно
someView.setNeedsDisplay()
someView.displayIfNeeded() // draw(_:) викликано негайно
Виклик 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 прапорець, який скидається лише після реального виконання draw(_:) в кінці run loop.
setNeedsDisplay() запитує перемалювання вмісту в’ю (виклик draw(_:)). setNeedsLayout() запитує перекомпонування сабв’ю (виклик layoutSubviews()). layoutSubviews() може призвести до зміни розмірів, що в свою чергу може викликати перемалювання.
setNeedsDisplay(_:) слід використовувати при точкових змінах: рух курсора, зміна кольору виділеної області, оновлення частини графіка. Це обмежує область перемалювання і підвищує продуктивність порівняно з повним перемалюванням всієї в’ю.
За замовчуванням зміна frame або bounds не викликає setNeedsDisplay(). Система лише переміщує в’ю. Якщо потрібне перемалювання при зміні розмірів, встановіть властивості contentMode значення .redraw — тоді UIKit буде автоматично викликати setNeedsDisplay() при кожній зміні bounds.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також