setNeedsDisplay(): istota metody i mechanizm przerysowania w iOS

Autor: IT Sectr Opublikowano: 2026-07-20 Czas czytania: 6 min

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() — odłożone żądanie przerysowania UIView, nie wywołuje draw(_:) synchronicznie
  • dirty flag — wewnętrzna flaga widoku, ustawiana przez setNeedsDisplay() i resetowana po draw(_:)
  • setNeedsDisplay(_:) — przeciążenie z CGRect dla częściowego przerysowania tylko wskazanego obszaru
  • displayIfNeeded() — wymuszone synchroniczne przerysowanie, używane do natychmiastowej aktualizacji
  • Cycle renderowania — system przetwarza dirty widoki na końcu run loop, łącząc wiele żądań w jeden przebieg

Czym jest setNeedsDisplay()

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.

Jak działa odłożone przerysowanie

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.

Run loop i kroki renderowania

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.

swift
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ą.

setNeedsDisplay() vs setNeedsDisplay(_:)

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.

CechasetNeedsDisplay()setNeedsDisplay(_:)
ParametrBrakCGRect — obszar przerysowania
Obszar przerysowaniaCały bounds widokuTylko wskazany prostokąt
WydajnośćNiższa przy dużych widokachWyższa przy częściowych zmianach
Parametr rect w draw(_:)Cały boundsWskazany prostokąt
Kiedy używaćZmiana całej zawartościPunktowe 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() a setNeedsDisplay()

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.

swift
    // 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(_:).

Optymalizacja wywołań setNeedsDisplay()

częste wywołania setNeedsDisplay() mogą obniżyć wydajność, szczególnie podczas animacji. Omówmy główne strategie optymalizacji.

Łączenie zmian przez model

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.

Używanie CALayer do animowalnych 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.

Zbiorcza aktualizacja przez CATransaction

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.

swift
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

Czy można wywołać draw(_:) bezpośrednio zamiast setNeedsDisplay()?

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.

Co się stanie przy wielokrotnych wywołaniach setNeedsDisplay()?

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.

Czym setNeedsDisplay() różni się od setNeedsLayout()?

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.

Kiedy używać setNeedsDisplay(_:)?

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.

Czy zmiana frame automatycznie wywołuje setNeedsDisplay()?

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

  • setNeedsDisplay() — asynchroniczna metoda UIView do żądania odłożonego przerysowania przez ustawienie dirty flag
  • setNeedsDisplay(_:) — przeciążenie z CGRect dla częściowego przerysowania tylko wskazanego obszaru, optymalizuje wydajność
  • displayIfNeeded() — synchroniczna metoda do natychmiastowego przerysowania dirty-widoków, używana tylko w ostateczności
  • Run loop — system przetwarza dirty-widoki na końcu bieżącego cyklu, łącząc wielokrotne wywołania w jeden przebieg draw(_:)
  • CALayer — dla animowalnych właściwości (position, opacity) używaj Core Animation zamiast draw(_:) — jest to o rząd wydajniejsze
  • CATransaction — grupuje wiele zmian w jeden cykl renderowania, zmniejszając liczbę wywołań draw(_:)
  • contentMode = .redraw — włącza automatyczne wywoływanie setNeedsDisplay() przy zmianie bounds widoku

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.

Omów projekt

Przeczytaj również