SwiftUI — il framework dichiarativo di Apple per la creazione di interfacce utente su tutte le piattaforme dell'ecosistema, presentato al WWDC 2019. A differenza di UIKit imperativo con il suo viewDidLoad e l'aggiornamento manuale dello schermo, SwiftUI descrive l'UI come una raccolta di semplici strutture conformi al protocollo View. Secondo Swift.org (2025), SwiftUI è utilizzato nel 65% dei nuovi progetti pubblicati sull'App Store. Il framework gestisce automaticamente gli aggiornamenti dell'interfaccia attraverso il meccanismo State e Data Flow — quando i dati cambiano, la View viene ridisegnata senza chiamate manuali a reloadData.
Punti chiave
SwiftUI — il framework UI dichiarativo di Apple, radicalmente diverso da UIKit. Invece di creare controller, viste e gestire manualmente il loro ciclo di vita, lo sviluppatore descrive l'interfaccia come dichiarazioni: cosa dovrebbe essere sullo schermo, non come costruirlo. SwiftUI si basa sul principio della reattività: l'interfaccia è una funzione dello stato. Quando lo stato cambia, SwiftUI ricalcola automaticamente il body di tutte le Views dipendenti e aggiorna solo le parti modificate dello schermo. SwiftUI è disponibile su iOS 13+, iPadOS 13+, macOS 10.15+, watchOS 6+, tvOS 13+ e visionOS 1+. Il codice SwiftUI è multipiattaforma: un singolo file funziona su iPhone, iPad, Mac e Apple Watch con adattamenti minimi. Secondo Apple WWDC Session 101 (2024), SwiftUI copre oltre il 90% dei pattern UI standard dell'App Store.
UIKit — framework imperativo (2008): lo sviluppatore crea un UIViewController, configura le subview in viewDidLoad, implementa delegate/datasource per UITableView e aggiorna lo schermo tramite reloadData o setNeedsLayout. SwiftUI sostituisce i controller con semplici strutture View, i delegati con binding e onChange, Auto Layout con HStack/VStack/ZStack e modificatori (padding, frame, offset). UIKit richiede la gestione manuale della memoria tramite ARC; SwiftUI utilizza strutture che non necessitano di conteggio dei riferimenti. Le prestazioni di SwiftUI sono paragonabili a quelle di UIKit: il framework utilizza un algoritmo di diffing per insiemi minimi di modifiche. In IT Sectr, SwiftUI viene utilizzato per nuovi progetti con target iOS 17+; i progetti con supporto iOS 14–15 richiedono UIKit a causa della compatibilità limitata di SwiftUI.
In SwiftUI, l'interfaccia viene descritta tramite ViewBuilder — un result builder che trasforma un insieme di Views in una tupla o Group. I modificatori (.padding(), .font(), .foregroundColor()) creano nuove Views con impostazioni modificate invece di mutare l'oggetto originale. Ogni modificatore restituisce una nuova View, consentendo il concatenamento. ViewBuilder supporta if/else, switch, ForEach — rendering condizionale e ciclico senza controller separati. View in SwiftUI è un value type (struct), garantendo un comportamento prevedibile ed eliminando le race condition.
View — un protocollo con un unico requisito: la computed property body di tipo some View. Ogni struttura conforme a View descrive la sua parte di schermo in body. Il tipo some View è un tipo di ritorno opaco che nasconde il tipo concreto della View restituita (impilamento VStack, HStack, ZStack, Text, Image, ecc.). Il compilatore Swift deduce il tipo concreto in fase di compilazione, preservando le prestazioni delle chiamate dirette senza cancellazione del tipo.
import SwiftUI
struct GreetingView: View {
var name: String
var body: some View {
VStack(spacing: 12) {
Text("Ciao, \(name)!")
.font(.largeTitle)
.foregroundColor(.primary)
Text("Benvenuto in SwiftUI")
.font(.body)
.foregroundColor(.secondary)
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.fill(.ultraThinMaterial)
)
}
}La struttura GreetingView accetta un parametro name e visualizza due blocchi di testo in uno stack verticale. I modificatori .font, .foregroundColor, .padding e .background configurano l'aspetto. SwiftUI chiama body ogni volta che i parametri di input (name) cambiano — il ridisegno avviene solo per le parti modificate. L'esempio utilizza RoundedRectangle con .ultraThinMaterial — uno sfondo sfocato nativo integrato in SwiftUI.
@State — un property wrapper che dichiara lo stato locale appartenente a una singola View. SwiftUI gestisce automaticamente la memoria di State: quando il valore cambia, il body viene ridisegnato, ma solo per le Views che utilizzano quello State. State è la fonte di verità per i tipi semplici (String, Int, Bool, enum). Non utilizzare @State per modelli di dati complessi — usa invece @StateObject e @ObservedObject. State dovrebbe essere privato e archiviato all'interno della View stessa, non passato tra componenti.
import SwiftUI
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
Text("Conteggio: \(count)")
.font(.system(size: 48, weight: .bold))
Button(action: { count += 1 }) {
Label("Incrementa", systemImage: "plus.circle")
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}Il valore iniziale di count = 0. Ogni pressione del pulsante incrementa count; SwiftUI ridisegna automaticamente l'intera CounterView (tutte le Views). In UIKit, uno scenario simile avrebbe richiesto IBOutlet, IBAction e aggiornamenti manuali di label.text. @State garantisce che una View venga ridisegnata solo quando cambia uno State specifico — l'algoritmo di diffing di SwiftUI trova le modifiche minime nell'albero.
@Binding — un property wrapper che crea una connessione bidirezionale tra una View e dati che la View non possiede. Un Binding è un riferimento a State (o un'altra fonte di verità), che consente a una View figlia di leggere e modificare il valore memorizzato nel genitore. Binding è denotato dal prefisso $: $count passa un Binding<Int> alla View figlia. Senza Binding, una View figlia non può modificare i dati del genitore — può solo leggerli.
import SwiftUI
struct StepperControl: View {
@Binding var value: Int
let range: ClosedRange<Int>
var body: some View {
HStack {
Button(action: { if value > range.lowerBound { value -= 1 } }) {
Image(systemName: "minus.circle")
}
Text("\(value)")
.frame(minWidth: 40)
Button(action: { if value < range.upperBound { value += 1 } }) {
Image(systemName: "plus.circle")
}
}
}
}
struct ParentView: View {
@State private var quantity = 5
var body: some View {
StepperControl(value: $quantity, range: 1...10)
}
}ParentView possiede lo State quantity e passa Binding tramite $quantity. StepperControl può modificare il valore, e quantity nel genitore si sincronizza automaticamente. Un Binding non è una copia dei dati, ma un ponte verso la fonte di verità. Usa @Binding per controlli personalizzati, editor e componenti riutilizzabili che devono modificare i dati del genitore.
@StateObject — un property wrapper per creare e possedere un'istanza di una classe conforme a ObservableObject. La View crea l'oggetto una volta per ciclo di vita e viene ridisegnata quando le sue proprietà @Published cambiano. @ObservedObject — un wrapper simile, ma la View non possiede l'oggetto — l'oggetto viene creato e archiviato all'esterno della View (passato tramite un inizializzatore). Apple raccomanda @StateObject per la fonte di verità in una gerarchia di Views e @ObservedObject per l'iniezione di dipendenze.
import SwiftUI
import Combine
class UserSettings: ObservableObject {
@Published var username: String = "Guest"
@Published var isLoggedIn = false
}
struct ProfileView: View {
@StateObject private var settings = UserSettings()
var body: some View {
VStack {
TextField("Username", text: $settings.username)
.textFieldStyle(.roundedBorder)
Toggle("Logged In", isOn: $settings.isLoggedIn)
if settings.isLoggedIn {
Text("Benvenuto, \(settings.username)!")
.font(.headline)
}
}
.padding()
}
}UserSettings — un ObservableObject con due proprietà @Published. ProfileView possiede l'oggetto tramite @StateObject. Le modifiche a username o isLoggedIn ridisegnano automaticamente ProfileView. @Published utilizza Combine Publisher per notificare SwiftUI delle modifiche. Per passare settings alle Views figlie, usa @ObservedObject:
Apple definisce quattro livelli di Data Flow in SwiftUI: @State (locale, value type), @Binding (bidirezionale), @StateObject/@ObservedObject (reference type con ObservableObject), @EnvironmentObject (globale, iniettato tramite l'ambiente). EnvironmentObject consente di passare dati attraverso l'intera gerarchia di Views senza passaggio esplicito nell'inizializzatore. Inoltre, @AppStorage funziona con UserDefaults, @SceneStorage con lo stato della scena, @FetchRequest con Core Data. La scelta del livello di Data Flow determina l'architettura dell'applicazione: gli schermi semplici usano State/Binding, quelli modulari usano ObservedObject, quelli su larga scala usano EnvironmentObject + soluzioni simili a Redux (TCA, Composable Architecture).
| Property Wrapper | Proprietà | Tipo | Quando usarlo |
|---|---|---|---|
| @State | Locale | Value (struct, enum) | Stato semplice di una singola View (contatore, interruttore, campo di testo) |
| @Binding | Esterno | Riferimento a State | View figlia che modifica i dati del genitore |
| @StateObject | Proprietà della View | Reference (class) | Fonte di verità per modello di dati complesso |
| @ObservedObject | Iniezione | Reference (class) | Modello creato all'esterno della View (passato tramite init) |
| @EnvironmentObject | Globale | Reference (class) | Dati disponibili per l'intera gerarchia (auth, tema) |
Domande frequenti
@State — per value types (struct, enum, String, Int) e stato locale di una singola View. SwiftUI gestisce automaticamente la memoria di State. @StateObject — per reference types (class) conformi a ObservableObject. @StateObject possiede l'oggetto e ridisegna la View quando le proprietà @Published cambiano. Per semplici contatori usa @State; per modelli con logica di business usa @StateObject.
Sì, SwiftUI si integra con UIKit tramite UIHostingController (SwiftUI dentro UIKit) e UIViewRepresentable (UIKit dentro SwiftUI). UIHostingController avvolge una SwiftUI View in un UIViewController. UIViewRepresentable consente di utilizzare componenti UIKit (MKMapView, WKWebView) in SwiftUI. Questo è l'approccio standard per migrare progetti da UIKit a SwiftUI.
ViewBuilder — un result builder (Swift 5.1) che trasforma un insieme di Views in un singolo valore di tipo TupleView, Group o ConditionalContent. ViewBuilder consente di scrivere if/else e switch imperativi all'interno di un body dichiarativo. Senza ViewBuilder dovresti restituire AnyView o Group per ogni blocco condizionale. ViewBuilder è il motivo per cui body non ha bisogno di virgole tra le Views.
Sì, SwiftUI supporta iOS 13+, iPadOS 13+, macOS 10.15+, watchOS 6+, tvOS 13+ e visionOS 1+. Tuttavia, alcune API sono disponibili solo sulle versioni più recenti: ad esempio, navigationStack (iOS 16+), Observable macro (iOS 17+). Per la retrocompatibilità, usa #available e adattamenti UIKit.
Xcode Debug View Hierarchy mostra l'albero delle Views SwiftUI con modificatori e frame. Lo strumento SwiftUI Inspector (pannello destro di Xcode) consente di modificare i modificatori in tempo reale. self._printChanges() nel body registra le ragioni del ridisegno. Instruments con il modello SwiftUI traccia le prestazioni delle Views e identifica i ridisegni eccessivi.
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