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. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође