Layer — to abstrakcja treści graficznej, która zarządza wizualną prezentacją elementów interfejsu w aplikacjach mobilnych. W przeciwieństwie do UIView, layer nie przetwarza zdarzeń dotyku i nie uczestniczy w Auto Layout — jego zadaniem jest wyłącznie renderowanie, animacja i kompozycja pikseli. Według Apple QuartzCore Documentation, 2025, każdy UIView w iOS ma powiązany CALayer, który faktycznie zarządza rysowaniem i animacją. Zrozumienie budowy warstw pozwala programiście kontrolować wydajność renderowania na poziomie pojedynczych pikseli.
Najważniejsze
Layer — to niskopoziomowy obiekt systemu graficznego, który przechowuje rastrowy obraz fragmentu ekranu i zarządza jego atrybutami wizualnymi: położeniem, rozmiarem, obrotem, przezroczystością, cieniem i kolorem. W iOS każdy UIView ma wbudowany CALayer, dostępny przez właściwość layer. Programista może pracować bezpośrednio z layer, pomijając UIView, w celu precyzyjnego dostrojenia renderowania.
Architektura warstw opiera się na wzorcu Model-View-Controller, gdzie CALayer pełni rolę Modelu — przechowuje stan właściwości wizualnych. Core Animation to Controller zarządzający animacją przejść między stanami. View (UIView) — opcjonalna otoczka dodająca obsługę dotyku i udział w Auto Layout.
Według Apple WWDC 2024, nowoczesny pipeline renderowania iOS używa Metal do kompozycji warstw. Każdy CALayer jest rysowany w osobnym buforze, po czym Core Animation scala wszystkie bufory w końcowy obraz z uwzględnieniem przezroczystości i trybów mieszania.
W Android odpowiednikiem warstw są View i tła Drawable, ale bezpośrednia praca z warstwami graficznymi jest dostępna przez Canvas i RenderNode w Android 10+. Zrozumienie koncepcji layer jest ważne dla optymalizacji renderowania na obu platformach.
Główna różnica polega na zakresie odpowiedzialności. View odpowiada za wprowadzanie danych przez użytkownika (dotyk, gesty), pozycjonowanie (Auto Layout, frame) i cykl życia. Layer odpowiada wyłącznie za reprezentację wizualną: renderowanie treści, animację właściwości i kompozycję z innymi warstwami.
To rozdzielenie pozwala buforować rastrową reprezentację warstwy niezależnie od View. Jeśli warstwa się nie zmienia, Core Animation używa zbuforowanej klatki, nie wywołując drawRect. W przypadku elementów statycznych daje to znaczący wzrost wydajności bez zmian w kodzie.
iOS udostępnia bogatą hierarchię klas dziedziczących po CALayer. Każda podklasa jest zoptymalizowana pod konkretny scenariusz: wyświetlanie tekstu, grafiki wektorowej, gradientów lub transformacji 3D. Wybór odpowiedniego typu warstwy bezpośrednio wpływa na wydajność renderowania.
W Android koncepcja warstw jest zaimplementowana przez RenderNode, ViewLayer i HardwareRenderer. Począwszy od Android 5.0 (API 21), każdy View jest renderowany we własną warstwę akceleracji sprzętowej, co pozwala wykonywać animacje bez wywoływania onDraw.
Dodatkową elastyczność w iOS dają CAReplicatorLayer i CAEmitterLayer. Pierwszy służy do tworzenia powtarzalnych wzorców, drugi — do systemów cząsteczek. Oba działają wyłącznie na GPU, co pozwala tworzyć złożone efekty wizualne bez utraty wydajności.
Hierarchia warstw w iOS budowana jest na zasadzie drzewa: każdy CALayer może zawierać wiele podrzędnych sublayers. Wszystkie transformacje zastosowane do warstwy nadrzędnej — skala, obrót, przesunięcie — są automatycznie stosowane do elementów podrzędnych. Zapewnia to spójność reprezentacji wizualnej podczas animacji.
Kompozycja sceny jest wykonywana przez Core Animation w następującej kolejności: najpierw renderowane jest tło (background), następnie każda warstwa podrzędna w kolejności dodawania od dołu do góry. Dla każdej warstwy Core Animation sprawdza właściwości przezroczystości, masek i trybów mieszania, po czym wykonuje scalanie (compositing) w końcową klatkę.
Według Apple Documentation, kompozycja warstwy z opaque = true i brakiem kanału alfa jest wykonywana bez dodatkowych przebiegów — GPU po prostu kopiuje piksele na poprzednią warstwę. Jeśli warstwa zawiera przezroczystość, GPU wykonuje alfa-blending, co wymaga dodatkowych zasobów obliczeniowych.
W Android kompozycja warstw odbywa się przez SurfaceFlinger — usługę systemową, która otrzymuje bufory od każdej aplikacji i scala je z uwzględnieniem kolejności z. Każde Window w Android to osobny Surface, który może zawierać wiele warstw graficznych.
CALayer.mask — właściwość, która pozwala zastosować do warstwy maskę o dowolnym kształcie. Maska może być dowolnym innym CALayer — na przykład CAShapeLayer z okrągłym path lub CAGradientLayer do tworzenia płynnego przejścia przezroczystości. Używanie masek zwiększa obciążenie GPU, ponieważ wymaga dodatkowego przebiegu renderowania do obliczenia wartości alfa każdego piksela.
Do prostego przycinania prostokątnych granic zaleca się używanie cornerRadius i masksToBounds. W przeciwieństwie do masek przez właściwość mask, cornerRadius jest przetwarzany sprzętowo na etapie rasteryzacji i nie wymaga dodatkowego przebiegu.
shouldRasterize — kluczowa właściwość CALayer do optymalizacji wydajności. Gdy shouldRasterize jest ustawiony na true, Core Animation renderuje warstwę i wszystkie jej sublayers do osobnego bufora rastrowego (offscreen buffer). Przy kolejnych klatkach zamiast ponownego renderowania używane jest zbuforowane zdjęcie rastrowe.
Buforowanie jest efektywne dla statycznych lub rzadko zmieniających się warstw: cieni, gradientów, tekstu z zaokrąglonymi rogami. Jednak dla często aktualizowanych warstw (animacja, wideo, przewijanie) rasteryzacja może pogorszyć wydajność, ponieważ każda klatka wymaga ponownego generowania bufora.
Według badań Objc.io, prawidłowe użycie shouldRasterize na iPad Pro skraca czas renderowania złożonego ekranu z 25 ms do 8 ms — ponad trzykrotnie. Kluczowy warunek: warstwa nie powinna zmieniać się częściej niż raz na 3-5 klatek.
Pierwszy przykład demonstruje konfigurację podstawowych właściwości wizualnych CALayer w Swift — zaokrąglanie rogów, cień i obramowanie:
import UIKit
class StyledView: UIView {
override func awakeFromNib() {
super.awakeFromNib()
layer.cornerRadius = 12
layer.masksToBounds = false
layer.shadowColor = UIColor.darkGray.cgColor
layer.shadowOpacity = 0.3
layer.shadowOffset = CGSize(width: 0, height: 4)
layer.shadowRadius = 8
}
}
Drugi przykład — tworzenie animacji warstwy w Swift z użyciem CABasicAnimation. Animowana jest właściwość position bez udziału UIView:
let animation = CABasicAnimation(keyPath: "position")
animation.fromValue = NSValue(cgPoint: CGPoint(x: 0, y: 0))
animation.toValue = NSValue(cgPoint: CGPoint(x: 150, y: 300))
animation.duration = 1.0
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
targetLayer.add(animation, forKey: "moveAnimation")
Trzeci przykład — praca z RenderNode w Kotlin na Android. RenderNode — bezpośredni odpowiednik CALayer, umożliwiający operowanie na warstwach graficznych na poziomie Canvas:
import android.graphics.renderer.RenderNode
fun createLayer(): RenderNode {
val node = RenderNode("customLayer")
node.setPosition(0, 0, 300, 200)
node.setScaleX(1.5f)
node.setScaleY(1.5f)
val canvas = node.beginRecording()
canvas.drawColor(android.graphics.Color.BLUE)
node.endRecording()
return node
}
Często zadawane pytania
UIView — to otoczka wokół CALayer, dodająca obsługę dotyku i udział w Auto Layout. Layer zajmuje się tylko renderowaniem i animacją. Praca bezpośrednio z CALayer jest możliwa, ale do obsługi zdarzeń wejściowych potrzebny jest UIView.
shouldRasterize włącza buforowanie warstwy w osobnym offscreen buffer. Używaj dla statycznych lub rzadko zmieniających się elementów z cieniami lub złożoną kompozycją. Nie używaj dla często animowanych warstw — ponowne generowanie bufora będzie droższe niż bezpośrednie renderowanie.
Ustaw właściwości cornerRadius dla zaokrąglenia rogów i masksToBounds = true dla przycięcia zawartości do granic warstwy. Dla cienia masksToBounds musi być false, w przeciwnym razie cień zostanie przycięty — w tym przypadku użyj osobnej warstwy dla cienia.
Tak, RenderNode w Android 10+ zapewnia podobną funkcjonalność: zarządzanie położeniem, skalą, obrotem i przezroczystością na poziomie warstwy graficznej. Canvas i HardwareRenderer zapewniają renderowanie przez GPU. Jednak architektonicznie Android nie rozdziela View i warstwy tak sztywno, jak iOS.
Cień w CALayer jest obliczany przez shadowPath lub automatycznie na podstawie kanału alfa. Automatyczne obliczanie wymaga przejścia przez wszystkie piksele warstwy, co jest kosztowne. Określ jawny shadowPath — prostokąt lub UIBezierPath — pozwoli to GPU obliczyć cień bez przeglądania treści.
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ż