draw(_:) i drawRect — co to, wywołanie i nadpisywanie

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

draw(_:) / drawRect — to metoda klasy UIView (w Swift) i jej odpowiednik w Objective-C (drawRect:), która odpowiada za renderowanie zawartości widoku z użyciem Core Graphics. System wywołuje tę metodę automatycznie przy pierwszym pojawieniu się widoku na ekranie oraz po wywołaniu setNeedsDisplay(). Według Apple Documentation (2025), draw(_:) jest jedyną metodą, w której programista ma dostęp do kontekstu graficznego bieżącego ekranu do niestandardowego rysowania. Nadpisanie draw(_:) daje pełną kontrolę nad wyglądem komponentu — od prostych figur geometrycznych po złożoną animowaną grafikę.

Najważniejsze

  • draw(_:) — metoda UIView do niestandardowego rysowania przez Core Graphics, nie jest wywoływana bezpośrednio przez programistę
  • drawRect: — odpowiednik draw(_:) w Objective-C, różni się składniowo, jest funkcjonalnie identyczny
  • CGContext — kontekst graficzny dostępny wewnątrz draw(_:) dla wszystkich operacji rysowania
  • setNeedsDisplay() — jedyny prawidłowy sposób żądania ponownego wywołania draw(_:) przez programistę
  • UIGraphicsGetCurrentContext() — funkcja pobierania bieżącego kontekstu wewnątrz draw(_:), obowiązkowy krok przed rysowaniem

Co to jest draw(_:) / drawRect

draw(_:) — to metoda instancji UIView, którą UIKit wywołuje do renderowania zawartości widoku. Wewnątrz tej metody programista uzyskuje dostęp do kontekstu graficznego CGContext i używa API Core Graphics do rysowania linii, wypełnień, tekstu i obrazów. drawRect: w Objective-C wykonuje tę samą funkcję, ale z inną składnią: jedynym parametrem jest CGRect określający obszar do przerysowania.

Według Apple Engineering (2024), draw(_:) działa przez CPU-renering Bitmap Graphics Context, co zapewnia maksymalną elastyczność, ale wymaga więcej zasobów w porównaniu z CALayer. Decyzja o użyciu draw(_:) jest podejmowana na podstawie złożoności grafiki i wymagań wydajnościowych.

Sygnatura metody w Swift i Objective-C

W Swift metoda jest deklarowana jako override func draw(_ rect: CGRect), gdzie rect to prostokąt wymagający przerysowania. W Objective-C sygnatura - (void)drawRect:(CGRect)rect. Parametr rect może być mniejszy niż bounds widoku przy częściowym przerysowaniu przez setNeedsDisplay(_:).

objective-c
- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
    CGContextFillRect(context, rect);
}

Kiedy system wywołuje draw(_:)

System automatycznie wywołuje draw(_:) w ściśle określonych scenariuszach. Zrozumienie tych wyzwalaczy pomaga uniknąć zbędnych przerysowań i zoptymalizować wydajność widoku. Poniżej wymieniono wszystkie przypadki automatycznego wywołania metody.

  • Pierwsze renderowanie — gdy widok jest po raz pierwszy dodawany do hierarchii i staje się widoczny na ekranie
  • setNeedsDisplay() — po wywołaniu tej metody system przy najbliższym cyklu rysowania
  • setNeedsDisplay(_:) — to samo, ale z określeniem konkretnego prostokąta do przerysowania
  • contentMode — przy zmianie bounds, jeśli contentMode wymaga przerysowania (np. .redraw)
  • setNeedsLayout() — w niektórych przypadkach po ponownym rozmieszczeniu subview może być wymagane przerysowanie

Dokumentacja Apple ostrzega: nigdy nie wywołuj draw(_:) bezpośrednio. System sam decyduje, kiedy wykonać rysowanie, a bezpośrednie wywołanie narusza wewnętrzny mechanizm buforowania. Aby zażądać przerysowania, zawsze używaj setNeedsDisplay() lub setNeedsDisplay(_:).

Jak nadpisać draw(_:) w Swift

Nadpisanie draw(_:) w Swift zaczyna się od pobrania kontekstu graficznego i następujących po nim wywołań Core Graphics. Zaleca się tworzenie oddzielnych metod dla logicznych bloków rysowania — poprawia to czytelność i testowalność kodu.

swift
override func draw(_ rect: CGRect) {
    super.draw(rect)

    guard let context = UIGraphicsGetCurrentContext() else { return }

    // Parametry linii
    context.setStrokeColor(UIColor.darkGray.cgColor)
    context.setLineWidth(2.0)

    // Rysowanie trójkąta
    context.move(to: CGPoint(x: rect.midX, y: rect.minY + 10))
    context.addLine(to: CGPoint(x: rect.maxX - 10, y: rect.maxY - 10))
    context.addLine(to: CGPoint(x: rect.minX + 10, y: rect.maxY - 10))
    context.closePath()
    context.strokePath()
}

W tym przykładzie draw(_:) rysuje trójkąt z ciemnoszarym konturem o grubości 2 pikseli. Wywołanie super.draw(rect) na początku metody jest zalecane przez Apple w celu zachowania rodzicielskiej logiki rysowania, chociaż domyślna implementacja draw(_:) w UIView jest pusta.

Zasada idempotentności draw(_:)

Każde wywołanie draw(_:) powinno dawać identyczny wynik przy tych samych danych wejściowych. Pozwala to systemowi buforować wynik i nie wywoływać draw(_:) ponownie, jeśli zawartość widoku się nie zmieniła. Nie używaj wewnątrz draw(_:) losowych wartości, czasu systemowego ani zapytań sieciowych.

drawRect: w Objective-C i różnica z draw(_:)

drawRect: — historycznie pierwsza wersja metody, która pojawiła się w iOS 2.0 wraz z Objective-C. W Swift metoda została przemianowana na draw(_:) z użyciem zewnętrznego parametru _. Funkcjonalnie metody są identyczne: obie otrzymują CGRect obszaru przerysowania i używają UIGraphicsGetCurrentContext() do dostępu do kontekstu graficznego.

Cechadraw(_:) (Swift)drawRect: (Objective-C)
Sygnaturaoverride func draw(_ rect: CGRect)- (void)drawRect:(CGRect)rect
Wywołanie supersuper.draw(rect)[super drawRect:rect]
KontekstUIGraphicsGetCurrentContext()UIGraphicsGetCurrentContext()
Parametr rectrect: CGRectCGRect rect
WydajnośćIdentycznaIdentyczna

Podczas migracji projektu z Objective-C na Swift przemianowanie metody jest jednym z pierwszych zadań. Xcode udostępnia automatyczny konwerter, ale drawRect: wymaga ręcznej aktualizacji do draw(_:). Według Apple (2024), wersja Swift draw(_:) jest preferowana dla nowych projektów.

Optymalizacja rysowania w draw(_:)

draw(_:) jest wykonywany na CPU, a nieoptymalna implementacja może powodować spadki klatek i niską wydajność. Apple zaleca kilka sprawdzonych podejść do przyspieszenia rysowania.

Minimalizuj liczbę operacji rysowania

Każda operacja Core Graphics (move(to:), addLine(to:), strokePath) ma narzut. Grupuj operacje i używaj CGPath dla złożonych kształtów — ścieżka jest tworzona raz i ponownie używana przy każdym wywołaniu draw(_:).

Używaj UIBezierPath dla obiektów wektorowych

UIBezierPath — to nakładka Objective-C na CGPath, która udostępnia prosty API do tworzenia kształtów. Twórz UIBezierPath z wyprzedzeniem (np. w inicjalizatorze) i po prostu wywołuj fill() lub stroke() wewnątrz draw(_:).

swift
private let starPath: UIBezierPath = {
    let path = UIBezierPath()
    // Budowanie kształtu gwiazdy
    path.move(to: CGPoint(x: 50, y: 0))
    for i in 1...5 {
        let angle = CGFloat(i) * 4 * CGFloat.pi / 5
        path.addLine(to: CGPoint(x: 50 + 40 * cos(angle),
                               y: 50 + 40 * sin(angle)))
    }
    path.close()
    return path
}()

override func draw(_ rect: CGRect) {
    UIColor.systemYellow.setFill()
    starPath.fill()
}

Typowe błędy przy pracy z draw(_:)

Programiści często popełniają te same błędy przy nadpisywaniu draw(_:). Znajomość tych wzorców pomaga uniknąć błędów i spadku wydajności. Przyjrzyjmy się najczęstszym problemom i ich rozwiązaniom.

  • Bezpośrednie wywołanie draw(_:) — nigdy nie wywołuj draw(_:) bezpośrednio. Używaj setNeedsDisplay() do żądania przerysowania. Bezpośrednie wywołanie psuje buforowanie i może prowadzić do nieprawidłowego wyświetlania.
  • Ciężkie obliczenia wewnątrz draw(_:) — draw(_:) powinien być maksymalnie lekki. Twórz UIBezierPath, obrazy i inne ciężkie obiekty poza metodą, inicjalizując je raz.
  • Tworzenie obiektów wewnątrz draw(_:) — konstrukcje UIColor, UIFont i UIGraphicsImageRenderer wewnątrz draw(_:) tworzą dodatkowe obciążenie pamięci. Przenieś tworzenie obiektów do właściwości klasy.
  • Ignorowanie parametru rect — rect wskazuje obszar wymagający przerysowania. Rysowanie poza rect jest odrzucane przez system, ale zużywa zasoby. Sprawdzaj intersection z rect przed rysowaniem.
  • Brak super.draw(rect) — chociaż implementacja UIView jest pusta, Apple zaleca wywoływanie super.draw(rect) dla kompatybilności z przyszłymi zmianami UIKit.

Postępując zgodnie z tymi zasadami, zapewnisz stabilne i szybkie rysowanie niestandardowych widoków w każdym projekcie iOS. Profiluj draw(_:) przez Instruments (Core Animation), aby zobaczyć rzeczywisty czas wykonania i wąskie gardła.

Często zadawane pytania

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

Nie, bezpośrednie wywołanie draw(_:) jest zabronione przez dokumentację Apple. System sam zarządza cyklem rysowania. Aby zażądać przerysowania, użyj setNeedsDisplay(), który prawidłowo oznacza widok jako wymagający aktualizacji w najbliższym cyklu renderowania.

Czym drawRect: różni się od draw(_:)?

Funkcjonalnie te metody są identyczne. drawRect: jest używany w Objective-C, draw(_:) — w Swift. Obie otrzymują CGRect obszaru przerysowania i używają tego samego kontekstu Core Graphics przez UIGraphicsGetCurrentContext().

Dlaczego draw(_:) nie jest wywoływany dla pustego UIView?

Apple optymalizuje renderowanie: jeśli UIView nie ma nadpisanego draw(_:), system nie tworzy dla niego kontekstu bitmapy. Oszczędza to pamięć. Jeśli nadpisanie istnieje, ale metoda nie jest wywoływana — sprawdź, czy ramka widoku nie jest zerowa i czy widok jest widoczny.

Jak często system wywołuje draw(_:)?

Tylko w razie potrzeby: pierwsze renderowanie, po setNeedsDisplay(), przy zmianie bounds z contentMode = .redraw. W stanie statycznym draw(_:) nie jest wywoływany ponownie, co oszczędza zasoby CPU i baterię.

Czy trzeba wywoływać super.draw(rect) w Swift?

Apple zaleca wywoływanie super.draw(rect) na początku nadpisanej metody. Chociaż obecna implementacja UIView jest pusta, wywołanie super zapewnia kompatybilność z przyszłymi wersjami UIKit i jest dobrą praktyką.

Podsumowanie

  • draw(_:) / drawRect — główna metoda rysowania UIView w Swift i Objective-C, wywoływana automatycznie przez system
  • CGContext — kontekst graficzny Core Graphics dostępny wewnątrz draw(_:) przez UIGraphicsGetCurrentContext()
  • setNeedsDisplay() — prawidłowy sposób żądania przerysowania; bezpośrednie wywołanie draw(_:) jest zabronione
  • Idempotentność — draw(_:) powinien dawać identyczny wynik przy tych samych danych wejściowych dla prawidłowego buforowania
  • UIBezierPath — twórz ścieżki poza draw(_:) aby zminimalizować obciążenie CPU przy każdym wywołaniu
  • Parametr rect — zawiera obszar przerysowania; używaj intersection do optymalizacji — nie rysuj poza jego granicami
  • Profilowanie — sprawdzaj wydajność draw(_:) przez Instruments Core Animation w celu wykrycia wolnych operacji

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ż