setNeedsDisplay() — je instanční metoda UIView, která označuje zobrazení jako vyžadující opětovné vykreslení. Na rozdíl od přímého volání draw(_:), setNeedsDisplay() pouze nastaví dirty příznak pro zobrazení a okamžitě vrátí řízení. Systém zpracuje tento požadavek v nejbližším renderovacím cyklu, což umožňuje sloučit několik volání do jedné operace kreslení. Podle Apple Documentation (2025) je odložené překreslení prostřednictvím setNeedsDisplay() jediným správným způsobem aktualizace obsahu vlastního UIView.
Hlavní body
setNeedsDisplay() — je metoda UIView, která signalizuje systému, že obsah zobrazení je zastaralý a vyžaduje nové vykreslení. Metoda nepřijímá parametry a vrací void. Po volání je zobrazení označeno jako dirty a v nejbližším kreslicím cyklu systém zavolá draw(_:) pro toto zobrazení.
Klíčovou vlastností je asynchronnost. Několik po sobě jdoucích volání setNeedsDisplay() pro stejné zobrazení nepovede k několikanásobným voláním draw(_:) — systém je sloučí do jedné operace. To je kriticky důležité pro výkon: pokud se vlastnost zobrazení změní několikrát za snímek, draw(_:) se volá pouze jednou.
Mechanismus odloženého překreslení je založen na renderovacím cyklu UIKit. Když je setNeedsDisplay() voláno, systém nastaví vnitřní příznak layer.needsDisplay na true. Na konci aktuálního run loop systém projde všechny dirty vrstvy a pro každou zavolá draw(_:).
UIKit run loop zahrnuje fázi update, během které se zpracovávají dirty zobrazení. V této fázi se v případě potřeby volá layoutSubviews(), poté draw(_:) pro všechna označená zobrazení. Po dokončení kreslení je výsledek předán na GPU přes Core Animation pro kompozici.
class ProgressView: UIView {
var progress: CGFloat = 0 {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let ctx = UIGraphicsGetCurrentContext() else { return }
// Pozadí
ctx.setFillColor(UIColor.lightGray.cgColor)
ctx.fill(rect)
// Indikátor průběhu
let fillRect = CGRect(x: rect.minX, y: rect.minY,
width: rect.width * progress, height: rect.height)
ctx.setFillColor(UIColor.systemGreen.cgColor)
ctx.fill(fillRect)
}
}
V tomto příkladu progressView aktualizuje progress a každý setter automaticky volá setNeedsDisplay(). Systém počká na konec run loop a zavolá draw(_:) s aktuální hodnotou progress. Pokud se progress změní třikrát v jednom snímku, draw(_:) se provede pouze jednou — s poslední hodnotou.
Metoda setNeedsDisplay(_:) přijímá parametr rect typu CGRect, který omezuje oblast překreslení. To umožňuje překreslit pouze změněnou část zobrazení, nikoli celé. Rozdíl ve výkonu je patrný při práci s velkými zobrazeními, kde změny ovlivňují malou oblast.
| Vlastnost | setNeedsDisplay() | setNeedsDisplay(_:) |
|---|---|---|
| Parametr | Ne | CGRect — oblast překreslení |
| Oblast překreslení | Celé bounds zobrazení | Pouze zadaný obdélník |
| Výkon | Nižší u velkých zobrazení | Vyšší u částečných změn |
| Parametr rect v draw(_:) | Celé bounds | Zadaný obdélník |
| Kdy použít | Změna celého obsahu | Bodové změny (kurzor, highlight) |
Pro složitou grafiku se doporučuje nejprve nakreslit celou scénu v offscreen kontextu a poté volat setNeedsDisplay(_:) pouze pro změněnou oblast. To je standardní technika v kreslicích aplikacích a grafických editorech na iOS.
displayIfNeeded() — je synchronní metoda, která okamžitě spustí překreslení všech dirty zobrazení v hierarchii, aniž by čekala na konec run loop. Na rozdíl od setNeedsDisplay(), které pouze nastaví příznak, displayIfNeeded() vynucuje okamžité provedení draw(_:).
Apple doporučuje používat displayIfNeeded() pouze v krajních případech, kdy je výsledek kreslení potřeba okamžitě — například před vytvořením snímku obrazovky nebo při synchronním generování obrázku pro UIGraphicsImageRenderer. V běžných scénářích je asynchronní model přes setNeedsDisplay() preferován, protože neblokuje hlavní vlákno.
// Asynchronní — doporučeno
someView.setNeedsDisplay()
// Synchronní — pouze v nezbytném případě
someView.setNeedsDisplay()
someView.displayIfNeeded() // draw(_:) voláno okamžitě
Volání displayIfNeeded() bez předchozího setNeedsDisplay() nebude mít účinek — metoda zpracovává pouze již označená dirty zobrazení. Pokud žádné zobrazení nevyžaduje překreslení, displayIfNeeded() skončí bez volání draw(_:).
Častá volání setNeedsDisplay() mohou snížit výkon, zejména při animaci. Podívejme se na hlavní optimalizační strategie.
Místo volání setNeedsDisplay() při každé změně jednotlivé vlastnosti shromažďujte změny a volejte metodu jednou po aplikování všech aktualizací. Například při změně barvy, velikosti a pozice prvku — zavolejte setNeedsDisplay() jednou po nastavení všech vlastností.
Pokud lze vlastnost animovat přes CALayer (backgroundColor, opacity, position, transform), použijte Core Animation místo draw(_:). Vlastnosti CALayer se aktualizují na GPU bez volání draw(_:), což poskytuje 60 FPS bez zatížení CPU.
Pro seskupení několika změn do jednoho renderovacího průchodu použijte CATransaction. To umožňuje sloučit několik setNeedsDisplay() do jedné operace draw(_:) na konci transakce.
CATransaction.begin()
CATransaction.setDisableActions(true)
view1.setNeedsDisplay()
view2.setNeedsDisplay()
view3.setNeedsDisplay()
CATransaction.commit()
Nezneužívejte displayIfNeeded() — synchronní překreslení blokuje hlavní vlákno a může způsobit ztrátu snímků. Používejte jej pouze pro operace, kde je výsledek kreslení kritický před dokončením aktuálního run loop.
Často kladené otázky
Ne, přímé volání draw(_:) je zakázáno. setNeedsDisplay() správně označí zobrazení jako dirty a systém zavolá draw(_:) ve správný okamžik renderovacího cyklu. Přímé volání draw(_:) ignoruje mechanismus ukládání do mezipaměti a může vést k nekonzistentnímu stavu.
Několik volání setNeedsDisplay() pro stejné zobrazení je systémem sloučeno do jednoho volání draw(_:). To se děje proto, že metoda pouze nastaví dirty flag, který se resetuje až po skutečném provedení draw(_:) na konci run loop.
setNeedsDisplay() požaduje překreslení obsahu zobrazení (volání draw(_:)). setNeedsLayout() požaduje nové uspořádání subview (volání layoutSubviews()). layoutSubviews() může vést ke změně rozměrů, což zase může vyvolat překreslení.
setNeedsDisplay(_:) by se mělo použít při bodových změnách: pohyb kurzoru, změna barvy vybrané oblasti, aktualizace části grafu. To omezuje oblast překreslení a zvyšuje výkon ve srovnání s úplným překreslením celého zobrazení.
Ve výchozím nastavení změna frame nebo bounds nevyvolá setNeedsDisplay(). Systém pouze přesune zobrazení. Pokud je vyžadováno překreslení při změně velikosti, nastavte vlastnost contentMode na hodnotu .redraw — pak UIKit bude automaticky volat setNeedsDisplay() při každé změně bounds.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také