# SwiftUI: Creare Interfacce Moderne per iOS > Una guida completa alla creazione di interfacce utente moderne con SwiftUI: sintassi dichiarativa, componenti, animazioni e best practice per iOS 18. - Published: 2026-01-05 - Updated: 2026-04-02 - Author: Anthony Fillion-Maillet - Tags: swiftui, ios, swift, ui, apple - Reading time: 12 min --- SwiftUI ha rivoluzionato lo sviluppo delle interfacce su tutte le piattaforme Apple. Grazie alla sua sintassi dichiarativa e all'integrazione nativa, questo framework consente di realizzare applicazioni eleganti con un numero di righe di codice notevolmente ridotto. Con iOS 18 arrivano miglioramenti significativi in termini di prestazioni e nuove funzionalita. > **Why SwiftUI in 2026?** > > SwiftUI ha raggiunto la piena maturita con iOS 18, offrendo rendering ottimizzato, gestione della memoria migliorata e un'integrazione con UIKit semplificata. Rappresenta lo standard per le nuove applicazioni iOS. ## Comprendere il Paradigma Dichiarativo Prima di scrivere codice, e fondamentale capire cosa rende SwiftUI diverso. Con UIKit (il framework precedente), lo sviluppatore doveva indicare a iOS **come** costruire l'interfaccia passo dopo passo: "crea un'etichetta, posizionala qui, cambia il colore quando l'utente tocca". Questo e il paradigma **imperativo**. SwiftUI funziona in modo diverso: lo sviluppatore descrive **cosa** vuole visualizzare, e il framework si occupa del resto. E come la differenza tra dare indicazioni stradali curva per curva (imperativo) e semplicemente indicare la destinazione (dichiarativo). ### La Prima View In SwiftUI, ogni elemento dell'interfaccia e una `View`. Una View e una struct che descrive cosa deve apparire sullo schermo. Ecco la prima schermata con un titolo, un sottotitolo e un pulsante: ```swift // ContentView.swift import SwiftUI // Each screen is a struct that implements the View protocol struct ContentView: View { // The "body" property describes what the view displays // "some View" means "a type of View, but Swift figures it out automatically" var body: some View { // VStack = "Vertical Stack": stacks elements vertically // spacing: 20 = 20 points of space between each element VStack(spacing: 20) { // A simple text with style modifiers Text("Welcome to SharpSkill") .font(.largeTitle) // Large title font .fontWeight(.bold) // Bold text Text("Prepare for your iOS interviews") .font(.subheadline) // Smaller font .foregroundColor(.secondary) // Gray secondary color // Button with an action (closure) and a label Button("Get Started") { print("Button tapped!") } .buttonStyle(.borderedProminent) // Filled blue button style } .padding() // Adds margins around the VStack } } ``` Da notare la struttura: si dichiarano gli elementi desiderati (testi, pulsante), si impilano verticalmente (VStack) e si applicano gli stili tramite **modifier** (`.font()`, `.padding()`). > **Concetto chiave**: In SwiftUI non si "creano" oggetti UI, si **descrive** l'interfaccia desiderata. SwiftUI gestisce autonomamente la creazione, l'aggiornamento e la distruzione degli elementi reali. ### Modifier: Trasformare le View I modifier sono metodi concatenati dopo una view per trasformarla. Funzionano come filtri applicati uno dopo l'altro. **L'ordine conta** perche ogni modifier crea una nuova view che avvolge la precedente. Ecco un esempio che illustra perche l'ordine e cruciale: ```swift // ModifiersOrder.swift // Example 1: padding THEN background Text("SwiftUI") .padding() // 1. Adds 16pt of space around the text .background(.blue) // 2. Blue background covers text + padding .foregroundColor(.white) // Result: white text on blue rectangle with margins // Example 2: background THEN padding (reversed order!) Text("SwiftUI") .background(.blue) // 1. Tight blue background around text only .padding() // 2. Transparent padding around blue background .foregroundColor(.white) // Result: white text on small blue rectangle, surrounded by empty space ``` La differenza? Nel primo caso, il padding e "dentro" lo sfondo. Nel secondo, e "fuori". Una sottigliezza essenziale per padroneggiare i layout. ## Organizzare le Interfacce con gli Stack SwiftUI offre tre contenitori principali per organizzare le view. Si possono immaginare come scatole che dispongono il contenuto in modi diversi. ### VStack, HStack e ZStack - **VStack** (Vertical Stack): impila gli elementi dall'alto verso il basso - **HStack** (Horizontal Stack): allinea gli elementi da sinistra a destra - **ZStack** (Z-axis Stack): sovrappone gli elementi uno sull'altro Costruiamo una card profilo utente combinando tutti e tre gli stack. L'obiettivo: mostrare una foto con un badge di verifica, quindi il nome e il ruolo dell'utente. ```swift // ProfileCard.swift struct ProfileCard: View { var body: some View { // Main HStack: photo on left, info on right HStack(spacing: 16) { // ZStack to overlay the badge on the photo ZStack(alignment: .bottomTrailing) { // Profile image (circle) Image("avatar") .resizable() .frame(width: 60, height: 60) .clipShape(Circle()) // Green "verified" badge at bottom right Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) .background(Circle().fill(.white)) // White circle behind } // VStack to stack name and role vertically VStack(alignment: .leading, spacing: 4) { Text("Marie Dupont") .font(.headline) Text("iOS Developer") .font(.subheadline) .foregroundColor(.secondary) } // Spacer pushes everything to the left Spacer() // Chevron on right indicates it's tappable Image(systemName: "chevron.right") .foregroundColor(.gray) } .padding() .background(Color(.systemGray6)) .cornerRadius(12) } } ``` Il trucco qui e `Spacer()`: e un elemento invisibile che occupa tutto lo spazio disponibile. Senza di esso, gli elementi sarebbero centrati. Con esso, vengono spinti a sinistra e il chevron resta ancorato a destra. > **Xcode Tip** > > Utilizzare `⌘ + click` su qualsiasi view in Xcode per accedere all'inspector visuale. Si possono aggiungere modifier senza scrivere codice! ## Gestire lo Stato con @State e @Binding La gestione dello stato e il cuore di SwiftUI. Lo stato e qualsiasi dato che puo cambiare e che deve aggiornare l'interfaccia. Quando lo stato cambia, SwiftUI ricalcola automaticamente le view interessate. ### @State: Lo Stato Locale di una View `@State` e un property wrapper che comunica a SwiftUI: "osserva questa variabile e aggiorna la view quando cambia". E perfetto per lo stato locale di una singola view. Ecco un contatore interattivo per comprendere il meccanismo: ```swift // CounterView.swift struct CounterView: View { // @State creates a "source of truth" for this view // private because state shouldn't be modified from outside @State private var count = 0 var body: some View { VStack(spacing: 30) { // This Text updates automatically when count changes Text("\(count)") .font(.system(size: 72, weight: .bold)) HStack(spacing: 40) { // Decrement button Button(action: { count -= 1 // Modifies state → view refreshes }) { Image(systemName: "minus.circle.fill") .font(.largeTitle) } // Increment button Button(action: { count += 1 }) { Image(systemName: "plus.circle.fill") .font(.largeTitle) } } } } } ``` Quando si tocca un pulsante, `count` cambia. SwiftUI rileva questa modifica e riesegue `body` per aggiornare la visualizzazione. Non e necessario aggiornare manualmente l'etichetta: e tutto automatico. ### @Binding: Condividere lo Stato tra View A volte una view figlia ha bisogno di modificare lo stato della view genitore. E qui che entra in gioco `@Binding`: crea una connessione bidirezionale a un `@State` esistente. Ecco un esempio concreto: un campo di input per il nome utente con validazione in tempo reale. ```swift // UsernameValidation.swift // Parent view: owns the state struct SignupForm: View { @State private var username = "" // Source of truth @State private var isValid = false // Validation state var body: some View { VStack(spacing: 20) { // We pass BINDINGS (with $) to the child view UsernameField(username: $username, isValid: $isValid) Button("Create Account") { // Submit the form } .disabled(!isValid) // Disabled if invalid .buttonStyle(.borderedProminent) } .padding() } } ``` La view figlia riceve i binding e puo modificarli: ```swift // Child view: receives and modifies state via @Binding struct UsernameField: View { @Binding var username: String // Connection to parent's @State @Binding var isValid: Bool var body: some View { VStack(alignment: .leading, spacing: 8) { TextField("Username", text: $username) .textFieldStyle(.roundedBorder) .onChange(of: username) { oldValue, newValue in // Validation: at least 3 characters isValid = newValue.count >= 3 } // Visual feedback HStack { Image(systemName: isValid ? "checkmark.circle" : "xmark.circle") Text("Minimum 3 characters") } .font(.caption) .foregroundColor(isValid ? .green : .red) } } } ``` Quando l'utente digita nel TextField, `username` viene modificato tramite il binding. La view genitore rileva la modifica e puo utilizzarla. Una comunicazione bidirezionale pulita. > **Warning** > > Utilizzare `@State` solo per stati semplici e locali. Per dati condivisi tra piu schermate o logica complessa, e preferibile ricorrere a `@Observable` (iOS 17+) o ai pattern MVVM. ## Visualizzare Liste Dinamiche Le liste sono onnipresenti nelle app mobile. SwiftUI mette a disposizione `List` per visualizzare collezioni di dati con lo stile nativo iOS (separatori, azioni di scorrimento, ecc.). ### Creare una Lista Semplice Per visualizzare una lista servono due cose: i dati e un modo per identificarli. Il protocollo `Identifiable` permette a SwiftUI di sapere quale view corrisponde a quale dato. Per prima cosa si definisce il modello dati: ```swift // Models/Interview.swift // Identifiable lets SwiftUI track each element struct Interview: Identifiable { let id = UUID() // Auto-generated unique identifier let technology: String let difficulty: String let questionCount: Int } ``` Ora la lista. L'idea e iterare sui dati con `ForEach` e creare una riga per ogni elemento: ```swift // InterviewListView.swift struct InterviewListView: View { // Data to display (in reality, this would come from an API) @State private var interviews = [ Interview(technology: "iOS", difficulty: "Intermediate", questionCount: 25), Interview(technology: "Android", difficulty: "Advanced", questionCount: 30), Interview(technology: "React", difficulty: "Beginner", questionCount: 20) ] var body: some View { // NavigationStack enables the navigation bar NavigationStack { List { // ForEach iterates over each interview // Thanks to Identifiable, no need to specify id: ForEach(interviews) { interview in InterviewRow(interview: interview) } } .navigationTitle("My Interviews") } } } ``` Ecco la view per ogni riga, estratta in un componente separato per chiarezza: ```swift // InterviewRow.swift struct InterviewRow: View { let interview: Interview var body: some View { HStack { // Left column: title and subtitle VStack(alignment: .leading, spacing: 4) { Text(interview.technology) .font(.headline) Text(interview.difficulty) .font(.subheadline) .foregroundColor(.secondary) } Spacer() // Badge with question count Text("\(interview.questionCount) Q") .font(.caption) .fontWeight(.medium) .padding(.horizontal, 8) .padding(.vertical, 4) .background(Color.blue.opacity(0.1)) .cornerRadius(8) } .padding(.vertical, 4) } } ``` Estrarre `InterviewRow` in una struct separata rende il codice piu leggibile e riutilizzabile. Si tratta di una best practice consolidata in SwiftUI. ### Aggiungere Azioni: Eliminazione e Riordino Le liste iOS supportano nativamente l'eliminazione (swipe a sinistra) e il riordino (drag and drop). SwiftUI rende queste operazioni banali con `.onDelete` e `.onMove`: ```swift // InterviewListView.swift (enhanced version) struct InterviewListView: View { @State private var interviews = [/* ... data ... */] var body: some View { NavigationStack { List { ForEach(interviews) { interview in InterviewRow(interview: interview) } // Swipe to delete .onDelete(perform: deleteInterview) // Drag and drop to reorder .onMove(perform: moveInterview) } .navigationTitle("My Interviews") .toolbar { // "Edit" button that activates edit mode EditButton() } } } // Deletes elements at specified indices private func deleteInterview(at offsets: IndexSet) { interviews.remove(atOffsets: offsets) } // Moves elements from one position to another private func moveInterview(from source: IndexSet, to destination: Int) { interviews.move(fromOffsets: source, toOffset: destination) } } ``` Con sole 4 righe di codice aggiuntive (`.onDelete`, `.onMove`, `EditButton` e le due funzioni), si ottiene una lista completamente interattiva. Questa e la potenza di SwiftUI. ## Animare le Interfacce SwiftUI eccelle nelle animazioni. A differenza di UIKit dove le animazioni richiedevano molto codice, qui tutto e dichiarativo: si descrive lo stato finale e SwiftUI anima la transizione. ### Animazioni Implicite con withAnimation Il modo piu semplice per animare e racchiudere un cambio di stato in `withAnimation`. SwiftUI rileva cosa cambia e anima automaticamente le proprieta visive interessate. ```swift // AnimatedCard.swift struct AnimatedCard: View { @State private var isExpanded = false var body: some View { VStack { RoundedRectangle(cornerRadius: 20) .fill(.blue) // Dimensions change based on state .frame( width: isExpanded ? 300 : 150, height: isExpanded ? 200 : 100 ) Button(isExpanded ? "Collapse" : "Expand") { // withAnimation animates ALL visual changes // resulting from this state change withAnimation(.spring(response: 0.5, dampingFraction: 0.7)) { isExpanded.toggle() } } .padding(.top) } } } ``` Quando si tocca il pulsante, `isExpanded` cambia valore. Grazie a `withAnimation`, il cambiamento di dimensione del rettangolo viene animato con un effetto a molla. Non e necessario specificare cosa animare: SwiftUI lo determina autonomamente. ### Transizioni: Animare Comparsa e Scomparsa Le transizioni definiscono come una view appare o scompare. Per impostazione predefinita si tratta di una dissolvenza (`opacity`), ma e possibile personalizzarla: ```swift // TransitionDemo.swift struct TransitionDemo: View { @State private var showDetails = false var body: some View { VStack(spacing: 20) { Button("Show Details") { withAnimation(.easeInOut(duration: 0.3)) { showDetails.toggle() } } // This view appears/disappears with a transition if showDetails { DetailCard() // Asymmetric transition: different for entry and exit .transition( .asymmetric( insertion: .scale.combined(with: .opacity), // Entry: zoom + fade removal: .slide // Exit: slide ) ) } } } } struct DetailCard: View { var body: some View { VStack(alignment: .leading, spacing: 12) { Text("Interview Details") .font(.headline) Text("25 questions • 45 minutes") .foregroundColor(.secondary) } .padding() .background(Color(.systemGray6)) .cornerRadius(16) } } ``` In questo caso, la card appare con uno zoom dal centro (scale + opacity) e scompare scivolando lateralmente (slide). Queste micro-animazioni rendono l'interfaccia viva e professionale. > **Performance** > > SwiftUI ottimizza automaticamente le animazioni. Preferire `.spring()` per un effetto naturale ed evitare animazioni troppo lunghe (> 0.5s) che possono frustrare l'utente. ## Caricare Dati Asincroni Nelle applicazioni reali, i dati provengono spesso da un'API. Swift Concurrency (async/await) si integra perfettamente con SwiftUI attraverso il modifier `.task`. ### Il Pattern Loading / Error / Success Ecco il pattern standard per visualizzare dati provenienti da un'API. Si gestiscono tre stati: caricamento, errore e successo. ```swift // AsyncDataView.swift struct AsyncDataView: View { @State private var questions: [Question] = [] @State private var isLoading = true @State private var errorMessage: String? var body: some View { Group { if isLoading { // State: loading in progress ProgressView("Loading...") } else if let error = errorMessage { // State: error VStack(spacing: 16) { Image(systemName: "exclamationmark.triangle") .font(.largeTitle) .foregroundColor(.orange) Text(error) Button("Retry") { Task { await loadQuestions() } } } } else { // State: success, display data List(questions) { question in Text(question.title) } } } // .task runs automatically when the view appears .task { await loadQuestions() } // Pull-to-refresh .refreshable { await loadQuestions() } } private func loadQuestions() async { isLoading = true errorMessage = nil do { // Async API call questions = try await QuestionService.shared.fetchQuestions() } catch { errorMessage = "Unable to load questions" } isLoading = false } } ``` Il modifier `.task` e essenziale: lancia un task asincrono quando la view appare e lo cancella automaticamente quando scompare. Nessun rischio di memory leak. ## Conclusione SwiftUI e diventato indispensabile per lo sviluppo iOS moderno. Con iOS 18, il framework ha raggiunto una maturita che lo rende perfettamente adatto alle applicazioni in produzione. ### Punti Chiave - **Paradigma dichiarativo**: descrivere cosa si vuole, non come costruirlo - **@State e @Binding**: gestire lo stato in modo reattivo e propagarlo tra le view - **Stack**: combinare VStack, HStack e ZStack per layout flessibili - **List**: visualizzare collezioni con codice minimale e interazioni native - **Animazioni**: utilizzare `withAnimation` per transizioni fluide automatiche - **Async/await**: caricare i dati con `.task` e gestire gli stati di caricamento/errore ### Checklist - Comprendere la differenza tra imperativo (UIKit) e dichiarativo (SwiftUI) - Padroneggiare i modifier e il loro ordine di applicazione - Sapere quando utilizzare @State vs @Binding vs @Observable - Costruire layout con gli Stack - Implementare liste con azioni (elimina, sposta) - Animare i cambiamenti di stato con withAnimation SwiftUI apre le porte alla creazione di applicazioni eleganti su tutto l'ecosistema Apple. Il modo migliore per imparare e mettere in pratica: creare un piccolo progetto personale e sperimentare con ogni concetto presentato in questo articolo. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/it/blog/ios/swiftui-building-modern-interfaces