setNeedsDisplay() — to metoda instancji UIView, która oznacza widok jako wymagający ponownego rysowania. W przeciwieństwie do bezpośredniego wywołania draw(_:), setNeedsDisplay() jedynie ustawia flagę dirty dla widoku i natychmiast zwraca sterowanie. System przetwarza to żądanie w najbliższym cyklu renderowania, co pozwala łączyć wiele wywołań w jedną operację rysowania. Według Apple Documentation (2025), odłożone przerysowanie przez setNeedsDisplay() jest jedynym poprawnym sposobem aktualizacji zawartości niestandardowego UIView.
Najważniejsze
setNeedsDisplay() — to metoda UIView, która sygnalizuje systemowi, że zawartość widoku jest nieaktualna i wymaga ponownego renderowania. Metoda nie przyjmuje parametrów i zwraca void. Po wywołaniu widok jest oznaczany jako dirty, a w najbliższym cyklu rysowania system wywołuje draw(_:) dla tego widoku.
Kluczową cechą jest asynchroniczność. Kilka kolejnych wywołań setNeedsDisplay() dla tego samego widoku nie spowoduje wielokrotnych wywołań draw(_:) — system łączy je w jedną operację. Jest to krytycznie ważne dla wydajności: jeśli właściwość widoku zmienia się kilka razy na klatkę, draw(_:) jest wywoływane tylko raz.
Mechanizm odłożonego przerysowania opiera się na cyklu renderowania UIKit. Gdy wywoływane jest setNeedsDisplay(), system ustawia wewnętrzną flagę layer.needsDisplay na true. Na końcu bieżącego run loop system przechodzi przez wszystkie dirty-warstwy i wywołuje draw(_:) dla każdej.
UIKit run loop zawiera fazę update, podczas której przetwarzane są dirty-widoki. W tej fazie następuje wywołanie layoutSubviews() w razie potrzeby, a następnie draw(_:) dla wszystkich oznaczonych widoków. Po zakończeniu rysowania wynik jest przekazywany do GPU przez Core Animation do kompozytowania.
class ProgressView: UIView {
var progress: CGFloat = 0 {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let ctx = UIGraphicsGetCurrentContext() else { return }
// Tło
ctx.setFillColor(UIColor.lightGray.cgColor)
ctx.fill(rect)
// Pasek postępu
let fillRect = CGRect(x: rect.minX, y: rect.minY,
width: rect.width * progress, height: rect.height)
ctx.setFillColor(UIColor.systemGreen.cgColor)
ctx.fill(fillRect)
}
}
W tym przykładzie progressView aktualizuje progress, a każdy setter automatycznie wywołuje setNeedsDisplay(). System zaczeka na koniec run loop i wywoła draw(_:) z aktualną wartością progress. Jeśli progress zmieni się trzykrotnie w jednej klatce, draw(_:) zostanie wykonane tylko raz — z ostatnią wartością.
Metoda setNeedsDisplay(_:) przyjmuje parametr rect typu CGRect, ograniczający obszar przerysowania. Pozwala to przerysowywać tylko zmienioną część widoku, a nie całość. Różnica w wydajności staje się zauważalna przy pracy z dużymi widokami, gdzie zmiany dotyczą małego obszaru.
| Cecha | setNeedsDisplay() | setNeedsDisplay(_:) |
|---|---|---|
| Parametr | Brak | CGRect — obszar przerysowania |
| Obszar przerysowania | Cały bounds widoku | Tylko wskazany prostokąt |
| Wydajność | Niższa przy dużych widokach | Wyższa przy częściowych zmianach |
| Parametr rect w draw(_:) | Cały bounds | Wskazany prostokąt |
| Kiedy używać | Zmiana całej zawartości | Punktowe zmiany (kursor, highlight) |
W przypadku złożonej grafiki zaleca się najpierw rysować całą scenę w offscreen kontekście, a następnie wywoływać setNeedsDisplay(_:) tylko dla zmienionego obszaru. To standardowa technika w aplikacjach do rysowania i edytorach graficznych na iOS.
displayIfNeeded() — to synchroniczna metoda, która natychmiast uruchamia przerysowanie wszystkich dirty-widoków w hierarchii, nie czekając na koniec run loop. W przeciwieństwie do setNeedsDisplay(), które jedynie ustawia flagę, displayIfNeeded() wymusza natychmiastowe wykonanie draw(_:).
Apple zaleca używanie displayIfNeeded() tylko w ostateczności, gdy wynik rysowania jest potrzebny natychmiast — na przykład przed utworzeniem zrzutu ekranu lub przy synchronicznym generowaniu obrazu dla UIGraphicsImageRenderer. W typowych scenariuszach model asynchroniczny przez setNeedsDisplay() jest preferowany, ponieważ nie blokuje głównego wątku.
// Asynchroniczny — zalecany
someView.setNeedsDisplay()
// Synchroniczny — tylko gdy konieczne
someView.setNeedsDisplay()
someView.displayIfNeeded() // draw(_:) wywołane natychmiast
Wywołanie displayIfNeeded() bez wcześniejszego setNeedsDisplay() nie przyniesie efektu — metoda przetwarza tylko już oznaczone jako dirty widoki. Jeśli żaden widok nie wymaga przerysowania, displayIfNeeded() zakończy się bez wywołania draw(_:).
częste wywołania setNeedsDisplay() mogą obniżyć wydajność, szczególnie podczas animacji. Omówmy główne strategie optymalizacji.
Zamiast wywoływać setNeedsDisplay() przy każdej zmianie pojedynczej właściwości, gromadź zmiany i wywołuj metodę raz po zastosowaniu wszystkich aktualizacji. Na przykład przy zmianie koloru, rozmiaru i pozycji elementu — wywołaj setNeedsDisplay() raz po ustawieniu wszystkich właściwości.
Jeśli właściwość może być animowana przez CALayer (backgroundColor, opacity, position, transform), używaj Core Animation zamiast draw(_:). Właściwości CALayer są aktualizowane na GPU bez wywoływania draw(_:), co zapewnia 60 FPS bez obciążania CPU.
Do grupowania wielu zmian w jeden przebieg renderowania używaj CATransaction. Pozwala to połączyć wiele setNeedsDisplay() w jedną operację draw(_:) na końcu transakcji.
CATransaction.begin()
CATransaction.setDisableActions(true)
view1.setNeedsDisplay()
view2.setNeedsDisplay()
view3.setNeedsDisplay()
CATransaction.commit()
Nie nadużywaj displayIfNeeded() — synchroniczne przerysowanie blokuje main thread i może spowodować spadek klatek. Używaj go tylko do operacji, gdzie wynik rysowania jest krytycznie ważny przed zakończeniem bieżącego run loop.
Często zadawane pytania
Nie, bezpośrednie wywołanie draw(_:) jest zabronione. setNeedsDisplay() poprawnie oznacza widok jako dirty, a system wywołuje draw(_:) w odpowiednim momencie cyklu renderowania. Bezpośrednie wywołanie draw(_:) ignoruje mechanizm buforowania i może prowadzić do niespójnego stanu.
Kilka wywołań setNeedsDisplay() dla tego samego widoku jest łączonych przez system w jedno wywołanie draw(_:). Dzieje się tak, ponieważ metoda jedynie ustawia dirty flag, który jest resetowany dopiero po rzeczywistym wykonaniu draw(_:) na końcu run loop.
setNeedsDisplay() żąda przerysowania zawartości widoku (wywołanie draw(_:)). setNeedsLayout() żąda ponownego rozmieszczenia subview (wywołanie layoutSubviews()). layoutSubviews() może prowadzić do zmiany rozmiarów, co z kolei może spowodować przerysowanie.
setNeedsDisplay(_:) należy używać przy punktowych zmianach: ruch kursora, zmiana koloru zaznaczonego obszaru, aktualizacja części wykresu. Ogranicza to obszar przerysowania i zwiększa wydajność w porównaniu z pełnym przerysowaniem całego widoku.
Domyślnie zmiana frame lub bounds nie wywołuje setNeedsDisplay(). System jedynie przesuwa widok. Jeśli wymagane jest przerysowanie przy zmianie rozmiaru, ustaw właściwości contentMode na wartość .redraw — wtedy UIKit będzie automatycznie wywoływać setNeedsDisplay() przy każdej zmianie bounds.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również