setNeedsDisplay() — este o metodă de instanță UIView care marchează vederea ca necesitând redesenare. Spre deosebire de apelul direct draw(_:), setNeedsDisplay() doar setează un flag dirty pentru vedere și returnează controlul imediat. Sistemul procesează această cerere în cel mai apropiat ciclu de randare, permițând combinarea mai multor apeluri într-o singură operație de desenare. Conform Apple Documentation (2025), redesenarea amânată prin setNeedsDisplay() este singura modalitate corectă de actualizare a conținutului unui UIView personalizat.
Principalele idei
setNeedsDisplay() — este o metodă UIView care semnalează sistemului că conținutul vederii este învechit și necesită rerandare. Metoda nu acceptă parametri și returnează void. După apel, vederea este marcată ca dirty, iar în cel mai apropiat ciclu de desenare sistemul apelează draw(_:) pentru această vedere.
Caracteristica cheie este asincronitatea. Mai multe apeluri consecutive setNeedsDisplay() pentru aceeași vedere nu vor duce la apeluri multiple draw(_:) — sistemul le combină într-o singură operație. Acest lucru este critic pentru performanță: dacă o proprietate a vederii se schimbă de mai multe ori pe cadru, draw(_:) este apelat o singură dată.
Mecanismul de redesenare amânată se bazează pe ciclul de randare UIKit. Când setNeedsDisplay() este apelat, sistemul setează flagul intern layer.needsDisplay la true. La sfârșitul run loop curent, sistemul parcurge toate straturile dirty și apelează draw(_:) pentru fiecare.
UIKit run loop include faza update, în care sunt procesate vederile dirty. În această fază, layoutSubviews() este apelat dacă este necesar, apoi draw(_:) pentru toate vederile marcate. După finalizarea desenării, rezultatul este transmis la GPU prin Core Animation pentru compozitare.
class ProgressView: UIView {
var progress: CGFloat = 0 {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let ctx = UIGraphicsGetCurrentContext() else { return }
// Fundal
ctx.setFillColor(UIColor.lightGray.cgColor)
ctx.fill(rect)
// Bara de progres
let fillRect = CGRect(x: rect.minX, y: rect.minY,
width: rect.width * progress, height: rect.height)
ctx.setFillColor(UIColor.systemGreen.cgColor)
ctx.fill(fillRect)
}
}
În acest exemplu, progressView actualizează progress, iar fiecare setter apelează automat setNeedsDisplay(). Sistemul așteaptă sfârșitul run loop și apelează draw(_:) cu valoarea curentă a progress. Dacă progress se schimbă de trei ori într-un cadru, draw(_:) se execută o singură dată — cu ultima valoare.
Metoda setNeedsDisplay(_:) acceptă parametrul rect de tip CGRect, care limitează zona de redesenare. Aceasta permite redesenarea doar a părții modificate a vederii, nu a întregului. Diferența de performanță devine vizibilă la lucrul cu vederi mari, unde modificările afectează o zonă mică.
| Caracteristică | setNeedsDisplay() | setNeedsDisplay(_:) |
|---|---|---|
| Parametru | Nu | CGRect — zona de redesenare |
| Zona de redesenare | Întreg bounds al vederii | Doar dreptunghiul specificat |
| Performanță | Mai scăzută la vederi mari | Mai ridicată la modificări parțiale |
| Parametrul rect în draw(_:) | Întreg bounds | Dreptunghiul specificat |
| Când să utilizați | Modificarea întregului conținut | Modificări punctuale (cursor, highlight) |
Pentru grafica complexă, se recomandă mai întâi desenarea întregii scene într-un context offscreen, apoi apelarea setNeedsDisplay(_:) doar pentru zona modificată. Aceasta este o tehnică standard în aplicațiile de desenare și editorii grafici pe iOS.
displayIfNeeded() — este o metodă sincronă care lansează imediat redesenarea tuturor vederilor dirty din ierarhie, fără a aștepta sfârșitul run loop. Spre deosebire de setNeedsDisplay(), care doar setează un flag, displayIfNeeded() forțează executarea imediată a draw(_:).
Apple recomandă utilizarea displayIfNeeded() doar în cazuri extreme, când rezultatul desenării este necesar imediat — de exemplu, înainte de a crea o captură de ecran sau la generarea sincronă a unei imagini pentru UIGraphicsImageRenderer. În scenariile obișnuite, modelul asincron prin setNeedsDisplay() este preferat, deoarece nu blochează thread-ul principal.
// Asincron — recomandat
someView.setNeedsDisplay()
// Sincron — doar când este necesar
someView.setNeedsDisplay()
someView.displayIfNeeded() // draw(_:) apelat imediat
Apelul displayIfNeeded() fără un setNeedsDisplay() prealabil nu va avea efect — metoda procesează doar vederile deja marcate ca dirty. Dacă nicio vedere nu necesită redesenare, displayIfNeeded() se încheie fără a apela draw(_:).
Apelurile frecvente setNeedsDisplay() pot reduce performanța, în special în timpul animației. Să examinăm principalele strategii de optimizare.
În loc să apelați setNeedsDisplay() la fiecare modificare a unei proprietăți individuale, acumulați modificările și apelați metoda o dată după aplicarea tuturor actualizărilor. De exemplu, la modificarea culorii, dimensiunii și poziției unui element — apelați setNeedsDisplay() o dată după setarea tuturor proprietăților.
Dacă o proprietate poate fi animată prin CALayer (backgroundColor, opacity, position, transform), utilizați Core Animation în loc de draw(_:). Proprietățile CALayer sunt actualizate pe GPU fără a apela draw(_:), oferind 60 FPS fără încărcarea CPU.
Pentru gruparea mai multor modificări într-o singură trecere de randare, utilizați CATransaction. Aceasta permite combinarea mai multor setNeedsDisplay() într-o singură operație draw(_:) la sfârșitul tranzacției.
CATransaction.begin()
CATransaction.setDisableActions(true)
view1.setNeedsDisplay()
view2.setNeedsDisplay()
view3.setNeedsDisplay()
CATransaction.commit()
Nu abuzați de displayIfNeeded() — redesenarea sincronă blochează main thread și poate cauza scăderea cadrelor. Utilizați-l doar pentru operațiile unde rezultatul desenării este critic înainte de finalizarea run loop curent.
Întrebări frecvente
Nu, apelul direct draw(_:) este interzis. setNeedsDisplay() marchează corect vederea ca dirty, iar sistemul apelează draw(_:) în momentul potrivit al ciclului de randare. Apelul direct draw(_:) ignoră mecanismul de cache și poate duce la o stare inconsistentă.
Mai multe apeluri setNeedsDisplay() pentru aceeași vedere sunt combinate de sistem într-un singur apel draw(_:). Aceasta se întâmplă deoarece metoda doar setează dirty flag, care este resetat doar după executarea reală a draw(_:) la sfârșitul run loop.
setNeedsDisplay() solicită redesenarea conținutului vederii (apel draw(_:)). setNeedsLayout() solicită rearanjarea subview-urilor (apel layoutSubviews()). layoutSubviews() poate duce la modificarea dimensiunilor, care la rândul său poate provoca redesenarea.
setNeedsDisplay(_:) trebuie utilizat la modificări punctuale: mișcarea cursorului, schimbarea culorii zonei selectate, actualizarea unei părți a graficului. Aceasta limitează zona de redesenare și crește performanța comparativ cu redesenarea completă a întregii vederi.
Implicit, modificarea frame sau bounds nu apelează setNeedsDisplay(). Sistemul doar mută vederea. Dacă este necesară redesenarea la modificarea dimensiunii, setați proprietatea contentMode la valoarea .redraw — atunci UIKit va apela automat setNeedsDisplay() la fiecare modificare a bounds.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și