SwiftUI è un framework dichiarativo di Apple per costruire interfacce utente su tutte le piattaforme dell'ecosistema. Invece di descrivere i passaggi in modo imperativo, lo sviluppatore dichiara come dovrebbe apparire l'interfaccia e SwiftUI gestisce il suo rendering e aggiornamento. Secondo la Apple Developer Documentation (2025), SwiftUI supporta iOS 15+, iPadOS 15+, macOS 12+, watchOS 8+ e tvOS 15+ e utilizza il View Protocol come blocco di costruzione di base per tutti i componenti dell'interfaccia.
Punti chiave
body è la base di qualsiasi componente UI SwiftUI, restituendo una descrizione dello schermo tramite composizione di viste.SwiftUI è un framework dichiarativo presentato da Apple nel 2019 per sostituire UIKit nei nuovi progetti. Invece di creare manualmente istanze UIView e aggiungerle alla gerarchia, lo sviluppatore descrive l'interfaccia attraverso strutture che conformano al protocollo View. SwiftUI calcola automaticamente la differenza tra lo stato corrente e quello nuovo e ridisegna solo le parti modificate utilizzando il proprio motore di rendering.
Il framework è scritto in Swift utilizzando value semantics (strutture, non classi), rendendo i componenti UI leggeri e thread-safe. A differenza di UIKit, dove UIViewController può pesare 200+ byte a causa del runtime Objective-C, una SwiftUI View è semplicemente una struttura di pochi byte. Questo è particolarmente importante per watchOS con la sua memoria limitata.
La stessa descrizione View funziona su iPhone, iPad, Mac, Apple Watch, Apple TV e Apple Vision Pro. SwiftUI adatta l'interfaccia alla piattaforma: gesti tattili su iOS, combinazioni di tastiera su macOS, scrolling Digital Crown su watchOS. Questo riduce i tempi di sviluppo per le aziende che rilasciano app su più piattaforme Apple, ma richiede configurazione aggiuntiva per elementi specifici di ogni piattaforma.
In SwiftUI, ogni schermata è una struttura che implementa il protocollo View con un unico requisito: una proprietà calcolata body di tipo some View. La parola chiave some (tipo opaco) nasconde il tipo concreto della vista, permettendo a SwiftUI di ottimizzare il rendering. All'interno di body, lo sviluppatore combina componenti pronti — Text, Image, Button, List — usando ViewBuilder, che assembla più viste in una sola.
struct GreetingView: View {
let name: String
var var body: some View {
VStack {
Text("Ciao, \(name)!")
.font(.title)
.foregroundColor(.blue)
Image(systemName: "hand.wave")
.imageScale(.large)
}
.padding()
}
}
Nell'esempio, VStack (stack verticale) contiene Text e Image. Il valore di name viene passato tramite l'inizializzatore della struttura — così funziona DI (Dependency Injection) in SwiftUI senza contenitori DI esterni. Ogni modificatore restituisce una nuova vista con la modifica applicata, senza mutare l'originale. Questo è possibile grazie all'immutabilità dei value types.
ViewBuilder è un result builder annotato con @resultBuilder che assembla fino a 10 viste in una. All'interno di body si possono usare if/else, switch e ForEach senza wrapper aggiuntivi. ForEach lavora con elementi Identifiable — a ogni vista viene assegnato un id univoco per un'animazione corretta durante inserimento/rimozione.
In SwiftUI, lo stato determina quale contenuto viene visualizzato sullo schermo. Quando lo stato cambia, SwiftUI ricrea il body della vista dipendente e confronta il risultato con il precedente usando un algoritmo di diff. Per memorizzare lo stato si usano property wrappers — ognuno risolve il suo compito specifico: stato locale, connessione con una vista figlia o modello dati esterno.
struct CounterView: View {
@State private var count = 0
var var body: some View {
VStack {
Text("Contatore: \(count)")
Button("Incrementa") {
count += 1
}
}
}
}
class UserViewModel: ObservableObject {
@Published var name = ""
@Published var age = 0
}
@State memorizza un valore locale semplice (Int, String, Bool) all'interno della struttura View. SwiftUI sposta la memoria dalla struttura a un archivio separato — quindi una proprietà con @State può essere mutata anche se View è un value type. @ObservableObject è per classi con proprietà @Published, i cui cambiamenti notificano automaticamente SwiftUI della necessità di ridisegnare.
@Binding crea una connessione bidirezionale con una fonte dati situata nella vista genitore. Il genitore passa $variable (projected value) e il figlio legge e scrive il valore attraverso il binding. Questo permette di spostare l'input di testo o un interruttore in un componente separato mantenendo lo stato nel genitore. Senza @Binding, ogni cambiamento richiederebbe una closure di callback per passare il nuovo valore verso l'alto.
Prima di iOS 16, la navigazione in SwiftUI era costruita su NavigationView — un'API legacy con comportamento complesso su iPad (split view, doppia colonna). A partire da iOS 16, Apple raccomanda NavigationStack — un'alternativa semplificata con route type-safe. Lo sviluppatore definisce un enum delle route possibili e NavigationStack gestisce automaticamente lo stack di schermate con supporto per deep link e ritorno alla radice.
enum Route: Hashable {
case detail(id: Int)
case settings
}
struct ContentView: View {
var var body: some View {
NavigationStack {
List {
NavigationLink("Schermata dettaglio",
value: Route.detail(id: 42))
NavigationLink("Impostazioni",
value: Route.settings)
}
.navigationDestination(for: Route.self) { route in
switch route {
case .detail(let id): DetailView(id: id)
case .settings: SettingsView()
}
}
}
}
}
Le route conformi a Hashable permettono di usare qualsiasi tipo di dato per passare parametri. navigationDestination(for:destination:) associa il tipo di route alla vista di destinazione. Il vantaggio rispetto alla navigazione UIKit è che non è richiesto ridisegno quando si aggiunge una nuova route: basta aggiungere un case all'enum e un handler nello switch. I deep link vengono gestiti tramite processDeepLink su NavigationStack.
Per la navigazione programmatica (dopo login, timer o risposta del server), si usa @State con l'inizializzatore NavigationLink: NavigationLink(isActive: $isActive). Quando isActive = true, la transizione avviene senza tocco dell'utente. Un'alternativa è il binding dell'array $path in NavigationStack: $path.append(Route.detail(id: 1)).
Modifier è un metodo che restituisce una copia modificata della vista. A differenza di UIKit, dove la configurazione delle proprietà avviene mutando una vista esistente, SwiftUI crea un nuovo valore con la modifica applicata. L'incatenamento dei modificatori costruisce l'interfaccia finale da trasformazioni sequenziali: font → padding → colore → ombra → gesto.
Apple fornisce oltre 200 modificatori integrati. I più comuni: .font(), .foregroundColor(), .padding(), .background(), .cornerRadius(), .shadow(), .opacity(), .offset(). L'ordine dei modificatori è importante: .padding() prima di .background() riempie l'area con il padding, dopo — solo l'area interna. Modificatori personalizzati vengono creati tramite il protocollo ViewModifier.
I modificatori possono essere applicati condizionalmente tramite l'operatore ternario: .foregroundColor(isError ? .red : .primary). Per l'animazione si usa .animation(.easeInOut, value: state) — il modificatore di animazione è legato a una proprietà di stato specifica. Quando questa proprietà cambia, SwiftUI anima la transizione tra il vecchio e il nuovo valore. L'animazione funziona con opacity, offset, scale, rotation, dimensione e colore — ogni proprietà ha un corrispondente AnimatableParameter.
Per animazioni personalizzate, sono disponibili .transition (comparsa/scomparsa) e .matchedGeometryEffect (transizione fluida di un elemento tra due contenitori). Quest'ultimo viene usato per le hero animation nelle liste: un'icona in una cella della lista si trasforma dolcemente in un'immagine grande nella schermata di dettaglio.
La scelta tra SwiftUI e UIKit è uno dei primi dilemmi dello sviluppatore iOS. Entrambi i framework sono supportati da Apple ma risolvono il problema della costruzione dell'interfaccia in modi fondamentalmente diversi: SwiftUI dichiarativamente, UIKit imperativamente. La differenza si manifesta nella gestione dello stato, navigazione, prestazioni e compatibilità.
| Aspetto | SwiftUI | UIKit |
|---|---|---|
| Approccio | Dichiarativo: cosa mostrare | Imperativo: come costruire |
| Stato | Property Wrappers, ridisegno automatico | Manuale: reloadData, setNeedsLayout |
| Codice UI | Compatto, catene di modificatori | Verboso, NSCoder/Storyboard/vincoli |
| Prestazioni | Alte su iOS 17+, algoritmo diff | Picco su iOS 12–16, controllo diretto |
| Versione minima | iOS 15+ (supporto completo) | iOS 2+ (tutte le versioni) |
Per i nuovi progetti con versione minima iOS 17, Apple raccomanda SwiftUI come framework principale. UIKit rimane necessario per interfacce che richiedono controllo fine sul rendering (UICollectionViewLayout personalizzato, scene CAAnimation complesse) o supporto per iOS 12–14. Molti progetti usano un approccio ibrido: SwiftUI tramite UIHostingController viene incorporato in un'app UIKit, e UIViewRepresentable permette di usare componenti UIKit all'interno della gerarchia SwiftUI.
Domande frequenti
Sì, tramite UIHostingController (SwiftUI in UIKit) e UIViewRepresentable (UIKit in SwiftUI). È un approccio ibrido, popolare durante la migrazione.
iOS 17 offre funzionalità complete: NavigationStack, Observation framework, Swift Charts. iOS 15 è la soglia minima per la produzione.
La causa più frequente è la modifica di una proprietà @Published su un thread secondario. ObservableObject deve inviare le modifiche sul main actor: @MainActor class ViewModel.
Usa .debounce tramite Combine: Button.publisher(for: .tap) .debounce(for: .seconds(0.3), scheduler: RunLoop.main).
Sì, tramite i modificatori Gesture: DragGesture, LongPressGesture, MagnificationGesture, RotationGesture. Combinali con .simultaneousGesture() e .sequenced().
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