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(_:) — 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.
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(_:).
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextFillRect(context, rect);
}
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.
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(_:).
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.
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.
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: — 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.
| Cecha | draw(_:) (Swift) | drawRect: (Objective-C) |
|---|---|---|
| Sygnatura | override func draw(_ rect: CGRect) | - (void)drawRect:(CGRect)rect |
| Wywołanie super | super.draw(rect) | [super drawRect:rect] |
| Kontekst | UIGraphicsGetCurrentContext() | UIGraphicsGetCurrentContext() |
| Parametr rect | rect: CGRect | CGRect rect |
| Wydajność | Identyczna | Identyczna |
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.
draw(_:) jest wykonywany na CPU, a nieoptymalna implementacja może powodować spadki klatek i niską wydajność. Apple zaleca kilka sprawdzonych podejść do przyspieszenia 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(_:).
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(_:).
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()
}
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.
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
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.
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().
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.
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ę.
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
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ż