setNeedsDisplay() è un metodo di istanza di UIView che segna la vista come bisognosa di essere ridisegnata. A differenza della chiamata diretta a draw(_:), setNeedsDisplay() imposta solo il flag dirty per la vista e restituisce immediatamente il controllo. Il sistema elabora questa richiesta nel ciclo di rendering più vicino, consentendo di combinare più chiamate in un'unica operazione di disegno. Secondo la Documentazione Apple (2025), il ridisegno differito tramite setNeedsDisplay() è l'unico modo corretto per aggiornare il contenuto di una UIView personalizzata.
Punti Chiave
setNeedsDisplay() è un metodo di UIView che segnala al sistema che il contenuto della vista è obsoleto e necessita di essere renderizzato nuovamente. Il metodo non accetta parametri e restituisce void. Dopo la chiamata, la vista viene marcata come dirty, e nel ciclo di disegno più vicino il sistema chiama draw(_:) per questa vista.
La caratteristica principale è l'asincronicità. Chiamate consecutive multiple a setNeedsDisplay() per la stessa vista non provocheranno chiamate multiple a draw(_:) — il sistema le combina in un'unica operazione. Questo è criticamente importante per le prestazioni: se una proprietà della vista cambia più volte per frame, draw(_:) viene chiamato una sola volta.
Il meccanismo di ridisegno differito si basa sul ciclo di rendering di UIKit. Quando viene chiamato setNeedsDisplay(), il sistema imposta il flag interno layer.needsDisplay su true. Alla fine del run loop corrente, il sistema scorre tutti i layer dirty e chiama draw(_:) per ciascuno.
Il run loop di UIKit include una fase di aggiornamento durante la quale vengono elaborate le viste dirty. In questa fase, viene chiamato layoutSubviews() se necessario, poi draw(_:) per tutte le viste marcate. Dopo il completamento del disegno, il risultato viene passato alla GPU tramite Core Animation per il compositing.
class ProgressView: UIView {
var progress: CGFloat = 0 {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let ctx = UIGraphicsGetCurrentContext() else { return }
// Sfondo
ctx.setFillColor(UIColor.lightGray.cgColor)
ctx.fill(rect)
// Barra di avanzamento
let fillRect = CGRect(x: rect.minX, y: rect.minY,
width: rect.width * progress, height: rect.height)
ctx.setFillColor(UIColor.systemGreen.cgColor)
ctx.fill(fillRect)
}
}
In questo esempio, progressView aggiorna progress e ogni setter chiama automaticamente setNeedsDisplay(). Il sistema attende la fine del run loop e chiama draw(_:) con il valore effettivo di progress. Se progress cambia tre volte per frame, draw(_:) viene eseguito solo una volta — con l'ultimo valore.
Il metodo setNeedsDisplay(_:) accetta un parametro rect di tipo CGRect, limitando l'area di ridisegno. Questo consente di ridisegnare solo la parte modificata della vista, invece dell'intera vista. La differenza di prestazioni diventa evidente quando si lavora con viste grandi dove le modifiche interessano una piccola area.
| Caratteristica | setNeedsDisplay() | setNeedsDisplay(_:) |
|---|---|---|
| Parametro | Nessuno | CGRect — area di ridisegno |
| Area di ridisegno | Tutti i bounds della vista | Solo il rettangolo specificato |
| Prestazioni | Inferiori per viste grandi | Superiori per modifiche parziali |
| Parametro rect in draw(_:) | Tutti i bounds | Rettangolo specificato |
| Quando usarlo | Modifica dell'intero contenuto | Modifiche puntuali (cursore, evidenziazione) |
Per grafica complessa, si consiglia di disegnare prima l'intera scena in un contesto offscreen, quindi chiamare setNeedsDisplay(_:) solo per l'area modificata. Questa è una tecnica standard nelle applicazioni di disegno e negli editor grafici su iOS.
displayIfNeeded() è un metodo sincrono che attiva immediatamente il ridisegno di tutte le viste dirty nella gerarchia, senza attendere la fine del run loop. A differenza di setNeedsDisplay(), che imposta solo un flag, displayIfNeeded() forza l'esecuzione immediata di draw(_:).
Apple raccomanda di utilizzare displayIfNeeded() solo in casi estremi quando il risultato del disegno è necessario immediatamente — ad esempio, prima di fare uno screenshot o durante la generazione sincrona di immagini per UIGraphicsImageRenderer. Negli scenari normali, il modello asincrono tramite setNeedsDisplay() è preferibile poiché non blocca il thread principale.
// Asincrono — raccomandato
someView.setNeedsDisplay()
// Sincrono — solo quando necessario
someView.setNeedsDisplay()
someView.displayIfNeeded() // draw(_:) chiamato immediatamente
Chiamare displayIfNeeded() senza un precedente setNeedsDisplay() non avrà effetto — il metodo elabora solo le viste già marcate come dirty. Se nessuna vista necessita di ridisegno, displayIfNeeded() termina senza chiamare draw(_:).
Chiamate frequenti a setNeedsDisplay() possono degradare le prestazioni, specialmente durante le animazioni. Esaminiamo le principali strategie di ottimizzazione.
Invece di chiamare setNeedsDisplay() ad ogni modifica di una proprietà individuale, accumula le modifiche e chiama il metodo una volta dopo aver applicato tutti gli aggiornamenti. Ad esempio, quando si modificano colore, dimensione e posizione di un elemento — chiama setNeedsDisplay() una volta dopo aver impostato tutte le proprietà.
Se una proprietà può essere animata tramite CALayer (backgroundColor, opacity, position, transform), usa Core Animation invece di draw(_:). Le proprietà CALayer vengono aggiornate sulla GPU senza chiamare draw(_:), fornendo 60 FPS senza carico sulla CPU.
Per raggruppare più modifiche in un unico passaggio di rendering, usa CATransaction. Ciò consente di combinare più chiamate setNeedsDisplay() in un'unica operazione draw(_:) alla fine della transazione.
CATransaction.begin()
CATransaction.setDisableActions(true)
view1.setNeedsDisplay()
view2.setNeedsDisplay()
view3.setNeedsDisplay()
CATransaction.commit()
Non abusare di displayIfNeeded() — il ridisegno sincrono blocca il thread principale e può causare cadute di fotogrammi. Usalo solo per operazioni in cui il risultato del disegno è critico prima della fine del run loop corrente.
Domande Frequenti
No, chiamare direttamente draw(_:) è vietato. setNeedsDisplay() marca correttamente la vista come dirty e il sistema chiama draw(_:) nel momento appropriato del ciclo di rendering. Chiamare direttamente draw(_:) ignora il meccanismo di caching e può portare a uno stato incoerente.
Chiamate multiple a setNeedsDisplay() per la stessa vista vengono combinate dal sistema in un'unica chiamata a draw(_:). Questo accade perché il metodo imposta solo il dirty flag, che viene cancellato solo dopo l'esecuzione effettiva di draw(_:) alla fine del run loop.
setNeedsDisplay() richiede un ridisegno del contenuto della vista (chiamata a draw(_:)). setNeedsLayout() richiede una riorganizzazione delle subview (chiamata a layoutSubviews()). layoutSubviews() può portare a cambiamenti di dimensione, che a loro volta possono innescare un ridisegno.
setNeedsDisplay(_:) dovrebbe essere usato per modifiche puntuali: movimento del cursore, cambio di colore di un'area selezionata, aggiornamento di parte di un grafico. Questo limita l'area di ridisegno e migliora le prestazioni rispetto al ridisegno dell'intera vista.
Per impostazione predefinita, cambiare il frame o i bounds non chiama setNeedsDisplay(). Il sistema sposta semplicemente la vista. Se il ridisegno è necessario quando le dimensioni cambiano, imposta la proprietà contentMode su .redraw — allora UIKit chiamerà automaticamente setNeedsDisplay() ad ogni cambio di bounds.
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