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 }
// Фон
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, и всеки setter автоматично извиква 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 flag, който се нулира едва след реалното изпълнение на draw(_:) в края на run loop.
setNeedsDisplay() иска прерисуване на съдържанието на изгледа (извикване на draw(_:)). setNeedsLayout() иска пренареждане на subview (извикване на layoutSubviews()). layoutSubviews() може да доведе до промяна на размерите, което от своя страна може да предизвика прерисуване.
setNeedsDisplay(_:) трябва да се използва при точкови промени: движение на курсора, промяна на цвета на избрана област, актуализация на част от графика. Това ограничава областта за прерисуване и повишава производителността в сравнение с пълното прерисуване на целия изглед.
По подразбиране промяната на frame или bounds не извиква setNeedsDisplay(). Системата просто премества изгледа. Ако се изисква прерисуване при промяна на размера, задайте свойството contentMode на стойност .redraw — тогава UIKit автоматично ще извиква setNeedsDisplay() при всяка промяна на bounds.
Заключение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също