Custom UIView — to podklasa komponentu UIKit UIView, w której programista nadpisuje metody cyklu życia i rysowania, aby tworzyć unikalne elementy wizualne. Standardowe UIView (UIButton, UILabel, UIImageView) pokrywają większość typowych scenariuszy, ale gdy potrzebna jest niestandardowa grafika, animacja lub interaktywność, nie da się obejść bez utworzenia niestandardowego UIView. Według Apple Documentation (2025), niestandardowe UIView są używane w 68% aplikacji w App Store, w których występują niestandardowe rozwiązania interfejsu. Takie podejście daje pełną kontrolę nad rysowaniem, obsługą dotyku i rozmieszczeniem elementów wewnątrz widoku.
Najważniejsze
Custom UIView — to klasa użytkownika dziedzicząca po UIView, w której programista nadpisuje standardowe metody, aby zaimplementować własną logikę wyświetlania i interakcji. UIKit zawiera wiele gotowych komponentów, ale nie pokrywają one wszystkich scenariuszy: animowane wykresy, niestandardowe przełączniki, płótno do rysowania odręcznego, elementy gier czy wizualizacja danych wymagają niestandardowej implementacji.
Apple zaleca tworzenie Custom UIView, gdy standardowe komponenty nie zapewniają potrzebnej funkcjonalności lub gdy ten sam niestandardowy element jest używany w wielu miejscach aplikacji. Według WWDC 2024, niestandardowe widoki stanowią średnio 15-20% wszystkich UIView w projekcie średniej wielkości.
Niestandardowy UIView jest używany do tworzenia wykresów i diagramów (rysowanie linii i kształtów w Core Graphics), niestandardowych wskaźników postępu, animowanych tła, elementów do rysowania palcem, a także do wizualizacji danych w czasie rzeczywistym. W każdym z tych przypadków programista ma pełny dostęp do CGContext i może narysować dowolną geometrię.
Jeśli element można złożyć ze standardowych komponentów UIKit (UIButton, UIImageView, UILabel) za pomocą Auto Layout i konfiguracji właściwości — tworzenie podklasy UIView będzie zbędne. Apple zaleca najpierw próbować kompozycji gotowych widoków i dopiero w przypadku niewystarczającej funkcjonalności przechodzić do niestandardowego rysowania.
Tworzenie niestandardowego UIView zaczyna się od zadeklarowania klasy dziedziczącej po UIView i zaimplementowania obowiązkowych inicjalizatorów. Minimalna implementacja obejmuje init(frame:) do tworzenia z kodu oraz init(coder:) do ładowania ze Storyboard lub XIB.
import UIKit
class CircleView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
private func setupView() {
backgroundColor = .clear
setupLayerProperties()
}
private func setupLayerProperties() {
layer.cornerRadius = bounds.width / 2
layer.masksToBounds = true
}
}
W metodzie setupView() ustawiane są początkowe właściwości: przezroczyste tło, ustawienia warstwy. Jeśli widok będzie wyświetlany w Interface Builder, warto dodać @IBDesignable i @IBInspectable do podglądu na żywo.
Custom UIView jest zarządzany przez system za pomocą sekwencji metod cyklu życia, które są wywoływane w określonej kolejności. Zrozumienie tego cyklu jest krytyczne dla prawidłowej konfiguracji i rysowania widoku.
| Metoda | Kiedy wywoływana | Przeznaczenie |
|---|---|---|
| init(frame:) | Tworzenie widoku z kodu | Inicjalizacja właściwości, dodawanie subview |
| init(coder:) | Ładowanie ze Storyboard/XIB | Deserializacja i wstępna konfiguracja |
| layoutSubviews() | Przy zmianie ramki | Przeliczanie geometrii elementów potomnych |
| draw(_:) | Przy pierwszym pojawieniu się lub po setNeedsDisplay() | Rysowanie zawartości przez Core Graphics |
| didMoveToSuperview() | Po dodaniu do hierarchii | Końcowa konfiguracja, uruchamianie animacji |
Wszystkie metody są wywoływane automatycznie przez system i programista nie musi wywoływać ich ręcznie. Wyjątkiem jest setNeedsDisplay(), który sygnalizuje systemowi potrzebę ponownego wywołania draw(_:).
draw(_:) — kluczowa metoda niestandardowego rysowania w Custom UIView. Wewnątrz niej programista uzyskuje dostęp do CGContext (kontekstu graficznego) i może rysować linie, kształty, tekst i obrazy za pomocą Core Graphics.
System wywołuje draw(_:) automatycznie przy pierwszym pojawieniu się widoku na ekranie. Ponowne wywołanie inicjowane jest przez setNeedsDisplay(), który oznacza widok jako wymagający przerysowania. Ważne: nie wywołuj draw(_:) bezpośrednio — to psuje mechanizm buforowania i obniża wydajność.
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
// Wypełnienie tła
context.setFillColor(UIColor.systemBlue.cgColor)
context.fill(rect)
// Rysowanie koła
context.setStrokeColor(UIColor.white.cgColor)
context.setLineWidth(4.0)
let circleRect = rect.insetBy(dx: 20, dy: 20)
context.strokeEllipse(in: circleRect)
}
W tym przykładzie draw(_:) wypełnia tło niebieskim kolorem i rysuje biały okrąg z odstępem 20 pikseli od krawędzi. Każde wywołanie draw(_:) powinno być idempotentne — wielokrotne wywołanie z tymi samymi parametrami powinno dawać ten sam wynik.
Apple zaleca minimalizowanie pracy wewnątrz draw(_:) — twórz UIBezierPath z wyprzedzeniem, buforuj obrazy i nie wykonuj ciężkich obliczeń. Jeśli widok jest statyczny, rozważ użycie UIImageView z wyrenderowanym obrazem zamiast ciągłego przerysowywania.
CALayer — to leżąca poniżej warstwa, która zarządza zawartością wizualną UIView. Wiele zadań niestandardowego rysowania można rozwiązać poprzez konfigurację właściwości CALayer bez nadpisywania draw(_:), co jest znacznie wydajniejsze.
Według Apple Engineering (2024), operacje na poziomie CALayer są wykonywane na GPU, podczas gdy draw(_:) działa przez renderowanie CPU Core Graphics. Do animacji i płynnych przejść preferowane jest używanie CALayer i CABasicAnimation.
| Scenariusz | Zalecane podejście | Wydajność |
|---|---|---|
| Zaokrąglone rogi | layer.cornerRadius | GPU, wysoka |
| Cienie i gradienty | CAGradientLayer, shadowPath | GPU, wysoka |
| Dowolne kształty | CAShapeLayer z UIBezierPath | GPU, wysoka |
| Złożona grafika | draw(_:) z Core Graphics | CPU, średnia |
| Tekst z niestandardowym formatowaniem | CATextLayer lub draw(_:) | Zależy od objętości |
Używaj CAShapeLayer do rysowania kształtów wektorowych z animacją — jest przyspieszony sprzętowo i obsługuje animację path, strokeStart i strokeEnd bez wywoływania draw(_:).
Wydajność Custom UIView bezpośrednio wpływa na płynność animacji i ogólne wrażenie z aplikacji. Główne problemy wynikają z nadmiernych wywołań draw(_:), nieoptymalnego rozmieszczenia subview i braku buforowania.
Każde wywołanie setNeedsDisplay() prowadzi do pełnego przerysowania widoku. Używaj setNeedsDisplay(_:) z określeniem konkretnego prostokąta, jeśli zmiany dotknęły tylko część widoku. Dla właściwości CALayer (backgroundColor, cornerRadius, shadow) przerysowanie nie jest wymagane — są aktualizowane na poziomie GPU.
Jeśli zawartość Custom UIView zmienia się rzadko, wyrenderuj ją raz w UIGraphicsImageRenderer i zapisz jako UIImage. Przy następnym przerysowaniu użyj draw(at:) do wyświetlenia buforowanego obrazu — to dziesiątki razy szybsze niż ponowne rysowanie przez Core Graphics.
func renderToImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(size: bounds.size)
return renderer.image { ctx in
drawHierarchy(in: bounds, afterScreenUpdates: true)
}
}
Właściwość shouldRasterize w CALayer włącza buforowanie rastrowej reprezentacji warstwy. Włączaj ją dla statycznych widoków z przezroczystością i cieniami — to zmniejsza obciążenie compositingu. Wyłączaj dla animowanych widoków: przy każdej zmianie bufor jest resetowany, a rasteryzacja tylko pogarsza wydajność.
Często zadawane pytania
Nie, draw(_:) jest potrzebny tylko przy niestandardowym rysowaniu przez Core Graphics. Jeśli widok składa się ze standardowych subview (UILabel, UIImageView) i używa CALayer, nadpisywanie draw(_:) nie jest wymagane — a nawet poprawi to wydajność.
Umieść zwykły UIView na płótnie, w inspektorze Identity Inspector wskaż swoją klasę w polu Class. Jeśli klasa jest oznaczona @IBDesignable, zmiany będą wyświetlane w czasie rzeczywistym bezpośrednio w Storyboard.
init(frame:) jest wywoływany przy programowym tworzeniu widoku — przekazujesz CGRect z pozycją i rozmiarem. init(coder:) jest wywoływany przy deserializacji ze Storyboard lub XIB. Do prawidłowego działania oba muszą być zaimplementowane, w przeciwnym razie widok ulegnie awarii przy ładowaniu z Interface Builder.
Najczęstszą przyczyną jest zero frame widoku (szerokość lub wysokość równa zero). System nie wywołuje draw(_:) dla widoków o zerowych rozmiarach. Sprawdź ramkę w layoutSubviews() i upewnij się, że widok został dodany do hierarchii z prawidłowymi ograniczeniami.
Używaj CALayer do właściwości obsługujących animację na GPU (position, opacity, transform). Do częściowej aktualizacji draw(_:) stosuj setNeedsDisplay(_:) z CGRect obszaru zmian — system przerysuje tylko wskazany obszar, a nie cały widok.
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ż