Core Animation è il framework di animazione di basso livello di Apple che opera a livello di CALayer e renderizza il contenuto sulla GPU. Core Animation è la base di tutte le animazioni in iOS e macOS: UIKit, AppKit, SceneKit e SpriteKit sono costruiti su di esso. Questo articolo spiega l’architettura di CALayer, CABasicAnimation, CATransaction e le tecniche pratiche.
Punti chiave
Core Animation è il framework grafico di Apple introdotto in Mac OS X 10.5 Leopard (2007) e portato su iOS con la prima versione di iPhone OS (2007). Core Animation gestisce la composizione, l’animazione e il rendering dei layer (CALayer) sulla GPU. Ogni UIView in iOS ha un CALayer integrato (view.layer) attraverso il quale avviene tutto il disegno.
Core Animation opera secondo il principio dell’animazione implicita: alcune modifiche delle proprietà di CALayer si animano automaticamente (CABasicAnimation per position, bounds, opacity, backgroundColor). Le animazioni implicite utilizzano CATransaction per impostazione predefinita con una durata di 0,25 secondi e una curva easeInOut.
Secondo Apple WWDC 2024, Core Animation elabora fino a 120 FPS sui dispositivi ProMotion, utilizzando Metal per il rendering GPU. L’impronta di memoria di un layer tipico è di 4 byte per pixel (RGBA) + ~100 byte di overhead per layer. Si consiglia di non superare i 1000 layer sullo schermo per 60 FPS stabili.
CALayer è il fondamento di Core Animation. Ogni CALayer contiene contenuto bitmap (o un riferimento a texture GPU) e definisce la geometria (bounds, position, anchorPoint, transform, cornerRadius, borderWidth, shadow). I layer non gestiscono gli eventi tattili — UIView/UIScrollView se ne occupano. I layer formano una gerarchia (layer tree) che rispecchia la gerarchia delle viste.
Core Animation mantiene tre alberi di layer: il model layer tree (proprietà effettive), il presentation layer tree (visualizzazione corrente durante l’animazione) e il render tree (dati GPU). Il presentation layer viene utilizzato per leggere i valori intermedi dell’animazione in tempo reale.
CALayer ha sottoclassi specializzate: CAShapeLayer (grafica vettoriale), CAGradientLayer (gradiente), CATextLayer (testo), CATiledLayer (immagini grandi divise in tile), CAEAGLLayer/MetalLayer (OpenGL/Metal). CAShapeLayer è il più popolare per l’animazione personalizzata grazie al supporto di CGPath e all’animazione strokeStart/strokeEnd.
CABasicAnimation è una sottoclasse di CAAnimation che anima una singola proprietà di CALayer da un valore iniziale (fromValue) a un valore finale (toValue) o mediante una modifica relativa (byValue). L’animazione viene aggiunta al layer tramite layer.add(animation, forKey:) e viene eseguita nel render server senza il coinvolgimento dell’app su ogni fotogramma.
La chiave (keyPath) è una stringa che specifica la proprietà: opacity, position, bounds, transform.rotation.z, cornerRadius, shadowOpacity, ecc. Core Animation supporta oltre 60 keyPath animabili. Per le proprietà personalizzate vengono utilizzati CATransaction e il presentation layer.
import QuartzCore
// CABasicAnimation per opacity e cornerRadius
func animateLayer() {
let layer = CALayer()
layer.frame = CGRect(x: 50, y: 50, width: 100, height: 100)
layer.backgroundColor = UIColor.systemBlue.cgColor
view.layer.addSublayer(layer)
// 1. Animazione dell’opacità
let fadeAnim = CABasicAnimation(keyPath: "opacity")
fadeAnim.fromValue = 1.0
fadeAnim.toValue = 0.2
fadeAnim.duration = 2.0
fadeAnim.autoreverses = true
fadeAnim.repeatCount = .infinity
// 2. Animazione del raggio d’angolo
let cornerAnim = CABasicAnimation(keyPath: "cornerRadius")
cornerAnim.fromValue = 0
cornerAnim.toValue = 50
cornerAnim.duration = 2.0
cornerAnim.autoreverses = true
cornerAnim.repeatCount = .infinity
layer.add(fadeAnim, forKey: "fade")
layer.add(cornerAnim, forKey: "corner")
}
// Animazione di position con ease-in-out
func moveLayer(_ layer: CALayer, to point: CGPoint) {
let anim = CABasicAnimation(keyPath: "position")
anim.fromValue = NSValue(cgPoint: layer.position)
anim.toValue = NSValue(cgPoint: point)
anim.duration = 0.6
anim.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
anim.isRemovedOnCompletion = false
anim.fillMode = .forwards
layer.add(anim, forKey: "move")
layer.position = point // aggiornamento del model layer
}Importante: CABasicAnimation è una visualizzazione temporanea (presentation layer). Al termine dell’animazione, il layer torna ai suoi valori del model layer. Per congelare lo stato finale, impostare isRemovedOnCompletion = false, fillMode = .forwards e aggiornare il model layer ai valori finali.
CATransaction è un meccanismo per raggruppare le animazioni di Core Animation in un’unica transazione. CATransaction.begin() e CATransaction.commit() definiscono i confini della transazione. All’interno di una transazione è possibile impostare parametri condivisi: duration, timingFunction, completionBlock, disableActions. CATransaction è il fondamento delle animazioni implicite di CALayer.
L’animazione implicita si verifica quando una proprietà di CALayer viene modificata al di fuori di un blocco di animazione: layer.opacity = 0,5. Core Animation crea automaticamente una CABasicAnimation con la durata della CATransaction corrente (default 0,25 s). Per disabilitare l’animazione implicita, utilizzare CATransaction.setDisableActions(true).
// CATransaction — raggruppamento e completamento
func groupedAnimation() {
CATransaction.begin()
CATransaction.setAnimationDuration(1.5)
CATransaction.setAnimationTimingFunction(
CAMediaTimingFunction(name: .easeOut)
)
CATransaction.setCompletionBlock {
print("All animations completed")
}
// Tutte le modifiche all’interno della transazione vengono animate con durata 1.5
layer1.opacity = 0.3
layer2.position = CGPoint(x: 200, y: 300)
layer3.backgroundColor = UIColor.systemRed.cgColor
CATransaction.commit()
}
// Disattivazione dell’animazione implicita
func updateWithoutAnimation() {
CATransaction.begin()
CATransaction.setDisableActions(true)
layer.frame = newFrame // senza animazione
CATransaction.commit()
}
// Animazione delle proprietà di CAShapeLayer
func animateProgress(percentage: CGFloat) {
CATransaction.begin()
CATransaction.setAnimationDuration(0.8)
CATransaction.setCompletionBlock {
print("Progress updated to (percentage)%")
}
progressLayer.strokeEnd = percentage / 100.0
CATransaction.commit()
}Core Animation si integra con UIKit attraverso il layer UIView. L’esempio seguente mostra un’animazione CAShapeLayer — un progresso circolare con animazione della lunghezza dell’arco e rotazione del layer tramite CABasicAnimation.
import UIKit
import QuartzCore
class CircularProgressView: UIView {
private let progressLayer = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
let path = UIBezierPath(
arcCenter: CGPoint(x: bounds.midX, y: bounds.midY),
radius: bounds.width / 2 - 10,
startAngle: -.pi / 2,
endAngle: 3 * .pi / 2,
clockwise: true
)
progressLayer.path = path.cgPath
progressLayer.strokeColor = UIColor.systemBlue.cgColor
progressLayer.lineWidth = 8
progressLayer.fillColor = nil
progressLayer.strokeEnd = 0
progressLayer.lineCap = .round
layer.addSublayer(progressLayer)
}
func setProgress(_ value: CGFloat, animated: Bool = true) {
if animated {
CATransaction.begin()
CATransaction.setAnimationDuration(0.6)
CATransaction.setAnimationTimingFunction(
CAMediaTimingFunction(name: .easeOut)
)
progressLayer.strokeEnd = min(max(value, 0), 1)
CATransaction.commit()
} else {
progressLayer.strokeEnd = value
}
}
}CAShapeLayer anima la proprietà strokeEnd da 0 a 1, creando un effetto di “riempimento” del progresso circolare. CATransaction con durata 0,6 e easeOut rende l’animazione fluida. layer.lineCap = .round aggiunge estremità arrotondate alla linea per un aspetto più pulito.
Domande frequenti
Core Animation funziona a livello di CALayer e viene controllato tramite CABasicAnimation e CATransaction. UIView.animate è un wrapper di alto livello attorno a Core Animation che crea automaticamente CABasicAnimation per le proprietà di UIView. Core Animation offre più controllo: keyPath, timingFunction, fillMode, raggruppamento tramite CATransaction.
L’animazione implicita è un’animazione automatica che si verifica quando una proprietà animabile di CALayer viene modificata al di fuori di un blocco di animazione. Ad esempio, layer.opacity = 0,5 crea una CABasicAnimation con durata 0,25 s. Le animazioni implicite sono gestite tramite CATransaction e possono essere disabilitate usando setDisableActions(true).
Per le proprietà personalizzate, implementare action(forKey:) in CALayer e restituire una CAAnimation. In alternativa, utilizzare CALayer.display() per il disegno manuale con animazione tramite CADisplayLink. Per le proprietà che supportano CABasicAnimation, è sufficiente specificare il keyPath come stringa.
CALayer è più leggero di UIView: non ha gestori di eventi (touches, gestures), non partecipa ad Auto Layout e non supporta l’accessibilità. Un CALayer tipico occupa ~100 byte contro ~300+ di UIView. Core Animation elabora i layer nel render server sulla GPU senza bloccare il thread principale.
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