CAShapeLayer è una sottoclasse di CALayer progettata per il rendering accelerato via hardware di tracciati vettoriali sulla GPU. A differenza di CALayer base, che lavora con immagini raster tramite contents, CAShapeLayer renderizza forme arbitrarie definite tramite CGPath senza creare un buffer offscreen separato. Secondo Apple QuartzCore Documentation, 2025, CAShapeLayer utilizza l'accelerazione hardware per l'anti-aliasing dei bordi e supporta l'animazione di path, strokeStart e strokeEnd. È uno strumento standard per creare icone, indicatori di progresso e maschere in iOS.
Punti Chiave
CAShapeLayer è un layer specializzato per il rendering di grafica vettoriale, introdotto in iOS 3.0. A differenza di CALayer, che memorizza un'immagine raster, CAShapeLayer memorizza una descrizione matematica di un tracciato — CGPath — e lo renderizza direttamente sulla GPU. Ciò consente di ridimensionare la forma senza perdita di qualità e di animare le sue proprietà senza ridisegnare il contenuto.
Architettonicamente, CAShapeLayer eredita tutte le capacità di CALayer: ombre, bordi, trasformazioni, maschere. Ma aggiunge proprietà specifiche per lavorare con i tracciati: path, fillColor, strokeColor, lineWidth, lineCap, lineJoin, miterLimit, strokeStart, strokeEnd e fillRule. Ciascuna di queste proprietà può essere animata tramite CABasicAnimation.
Secondo Apple Documentation, CAShapeLayer utilizza l'accelerazione hardware per il rendering. A differenza del disegno tramite Core Graphics in drawRect:, dove la CPU renderizza il tracciato in un contesto raster, CAShapeLayer invia comandi direttamente alla GPU tramite Metal. Il risultato è prestazioni più elevate, specialmente durante l'animazione.
CAShapeLayer è particolarmente utile per elementi dell'interfaccia che cambiano forma in risposta alle azioni dell'utente: indicatori di progresso, grafici, icone della barra delle schede, maschere per animazioni di rivelazione del contenuto.
Il processo di rendering di CAShapeLayer differisce da quello di CALayer base. Invece di memorizzare un buffer raster con pixel, CAShapeLayer passa una descrizione dei vertici del tracciato alla GPU — un insieme di punti, curve di Bézier e segmenti di linea. La GPU rasterizza questo tracciato in un unico passaggio, applicando i colori di riempimento e tratto nella fase dello shader di frammento.
Questo approccio offre due vantaggi: ridimensionamento senza perdita di qualità (la grafica vettoriale rimane nitida a qualsiasi risoluzione) e animazione efficiente — quando il tracciato cambia, la GPU ricalcola solo i pixel modificati, non l'intero schermo.
path — la proprietà centrale di CAShapeLayer, che accetta un CGPath. Il tracciato può contenere linee, archi, curve di Bézier e rettangoli. Viene creato tramite UIBezierPath in Swift o CGPath in Core Graphics. Esempio: UIBezierPath(roundedRect: rect, cornerRadius: 16).cgPath crea un rettangolo arrotondato.
La proprietà lineDashPattern consente di creare linee tratteggiate. Accetta un array di numeri: [6, 3] significa 6 punti di tratto e 3 punti di spazio. lineDashPhase imposta l'offset del pattern e può essere animato per un effetto di tratto in movimento.
La scelta tra CAShapeLayer e CALayer base dipende dal tipo di contenuto. Se l'elemento è un'area rettangolare con sfondo, bordo e ombra — CALayer è sufficiente. Se l'elemento ha una forma arbitraria, richiede animazione del tracciato o ridimensionamento senza perdita di qualità — CAShapeLayer è preferibile.
Anche le prestazioni differiscono. CALayer con cornerRadius e masksToBounds crea un buffer offscreen per il ritaglio del contenuto, aggiungendo 2–5 ms per fotogramma. CAShapeLayer non richiede un buffer offscreen — il ritaglio viene eseguito a livello dello shader di vertice. Per elementi con maschere complesse, CAShapeLayer può essere 2–3 volte più veloce.
Combinare CAShapeLayer con altri layer è un pattern comune. Ad esempio, CAGradientLayer usa CAShapeLayer come maschera per creare una forma con riempimento sfumato. Questo approccio offre sia qualità vettoriale che riempimento sfumato senza perdita di prestazioni.
L'animazione di path è una delle funzionalità più potenti di CAShapeLayer. Se entrambi i tracciati (iniziale e finale) hanno lo stesso numero di punti di controllo e segmenti, Core Animation esegue un'interpolazione fluida tra di essi. Il risultato è un effetto di morphing da una forma all'altra.
Condizione di compatibilità del tracciato: l'UIBezierPath iniziale e finale devono avere la stessa struttura — il numero di chiamate moveTo, addLine, addCurve e addQuadCurve deve corrispondere. Se la struttura è diversa, l'animazione sarà istantanea, senza interpolazione.
Secondo Apple WWDC 2024, la combinazione di strokeStart e strokeEnd è il pattern di animazione CAShapeLayer più utilizzato. Animare strokeEnd da 0.0 a 1.0 crea un effetto di disegno del tracciato dall'inizio alla fine. Viene utilizzato negli indicatori di caricamento, nelle animazioni di rivelazione dei grafici e nelle transizioni personalizzate tra schermate.
Per creare un effetto pulsante, animate strokeEnd e opacity contemporaneamente con diverse funzioni di temporizzazione. Per un effetto di formiche in marcia, animate lineDashPhase con un lineDashPattern fisso. Entrambe le tecniche sono implementate tramite CABasicAnimation senza coinvolgimento della CPU.
CAShapeLayer è spesso usato come maschera per altri layer. Ad esempio, per l'animazione di rivelazione del contenuto: inizialmente, mask.path è un rettangolo di dimensioni zero, e quello finale è a grandezza naturale. Core Animation interpola il tracciato e il contenuto si rivela dolcemente.
Questo pattern viene utilizzato in iOS per l'animazione di espansione delle carte, la presentazione di finestre modali e le transizioni tra controller di visualizzazione. A differenza dell'animazione alpha, l'animazione della maschera CAShapeLayer offre un effetto di rivelazione più naturale con forme arbitrarie.
Il primo esempio — creazione di un indicatore di progresso circolare con animazione strokeEnd. Questo è un elemento UI standard che dimostra le capacità principali di CAShapeLayer:
func createProgressIndicator(in view: UIView) -> CAShapeLayer {
let circlePath = UIBezierPath(
arcCenter: CGPoint(x: 50, y: 50),
radius: 40,
startAngle: -.pi / 2,
endAngle: 3 * .pi / 2,
clockwise: true
)
let trackLayer = CAShapeLayer()
trackLayer.path = circlePath.cgPath
trackLayer.strokeColor = UIColor.systemGray5.cgColor
trackLayer.fillColor = nil
trackLayer.lineWidth = 8
view.layer.addSublayer(trackLayer)
let progressLayer = CAShapeLayer()
progressLayer.path = circlePath.cgPath
progressLayer.strokeColor = UIColor.systemBlue.cgColor
progressLayer.fillColor = nil
progressLayer.lineWidth = 8
progressLayer.lineCap = .round
progressLayer.strokeEnd = 0
view.layer.addSublayer(progressLayer)
return progressLayer
}
// Anima al 75%
progressLayer.animateStrokeEnd(to: 0.75, duration: 1.5)
Il secondo esempio — animazione di morphing tra un cerchio e un quadrato. Entrambi gli UIBezierPath hanno una struttura compatibile:
let morphLayer = CAShapeLayer()
morphLayer.fillColor = UIColor.systemPurple.cgColor
morphLayer.frame = rect
let circlePath = UIBezierPath(roundedRect: rect, cornerRadius: rect.width / 2)
let squarePath = UIBezierPath(roundedRect: rect, cornerRadius: 0)
morphLayer.path = circlePath.cgPath
let morphAnimation = CABasicAnimation(keyPath: "path")
morphAnimation.toValue = squarePath.cgPath
morphAnimation.duration = 0.6
morphAnimation.autoreverses = true
morphAnimation.repeatCount = .infinity
morphLayer.add(morphAnimation, forKey: "morph")
Il terzo esempio — una maschera CAShapeLayer per l'animazione di rivelazione del contenuto. La maschera si espande dal centro verso i bordi:
func revealContent(_ view: UIView) {
let maskLayer = CAShapeLayer()
let size = view.bounds.size.width * 1.5
let center = CGPoint(x: view.bounds.midX, y: view.bounds.midY)
let initialPath = UIBezierPath(arcCenter: center, radius: 0, startAngle: 0, endAngle: .pi * 2, clockwise: true)
let finalPath = UIBezierPath(arcCenter: center, radius: size, startAngle: 0, endAngle: .pi * 2, clockwise: true)
maskLayer.path = initialPath.cgPath
view.layer.mask = maskLayer
let animation = CABasicAnimation(keyPath: "path")
animation.toValue = finalPath.cgPath
animation.duration = 0.4
animation.timingFunction = CAMediaTimingFunction(name: .easeOut)
maskLayer.add(animation, forKey: "reveal")
maskLayer.path = finalPath.cgPath
}
Domande Frequenti
drawRect utilizza la CPU per renderizzare in un contesto raster — è lento per le animazioni. CAShapeLayer renderizza il tracciato sulla GPU, offrendo 60 FPS durante le animazioni di path, strokeEnd e trasformazioni. Inoltre, CAShapeLayer non richiede ridisegno quando viene ridimensionato — la forma si ridimensiona vettorialmente.
Un'animazione fluida del tracciato richiede struttura identica dell'UIBezierPath iniziale e finale. Il numero di chiamate moveTo, addLine, addCurve deve corrispondere. Utilizzare UIBezierPath con lo stesso numero di segmenti per le forme iniziale e finale.
Impostare lineDashPattern — un array di numeri che alternano lunghezze di tratto e spazio. Ad esempio, [8, 4] crea tratti da 8pt con spazi da 4pt. Per un effetto di formiche in marcia, animare lineDashPhase tramite CABasicAnimation con una funzione di temporizzazione lineare.
Sì, questo è uno dei casi d'uso principali. Assegnare CAShapeLayer alla proprietà mask di qualsiasi CALayer, inclusi CAGradientLayer e altre sottoclassi. Animare il tracciato della maschera crea un effetto di rivelazione fluida del contenuto attraverso una forma arbitraria.
Creare un CAGradientLayer e impostare il suo mask = CAShapeLayer con il tracciato desiderato. Il gradiente sarà visibile solo all'interno del tracciato della maschera. Questo pattern combina i vantaggi della forma vettoriale di CAShapeLayer e il riempimento sfumato di CAGradientLayer.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche