Liquid Glass in SwiftUI per iOS 26: Guida Completa al Nuovo Design di Apple

Come usare il Liquid Glass in SwiftUI per iOS 26: .glassEffect(), GlassEffectContainer, morphing con glassEffectID, stili bottone, tinte, animazioni e best practice con esempi di codice pronti all'uso.

Introduzione

Con iOS 26, Apple ha fatto sul serio. Il Liquid Glass è il cambiamento visuale più importante dal flat design di iOS 7 — e no, non è il solito effetto blur con un nome più figo. Si tratta di un vero meta-materiale digitale che piega e concentra la luce attraverso un meccanismo chiamato lensing, creando superfici traslucide che reagiscono al contenuto sottostante in tempo reale.

Se avete già provato a compilare un'app con Xcode 26, probabilmente avrete notato che NavigationBar, TabBar, toolbar e sheet si sono trasformati da soli. Bene, quella è solo la punta dell'iceberg.

La vera potenza emerge quando si applica il Liquid Glass a viste personalizzate: bottoni fluttuanti, pannelli di controllo, interfacce di navigazione su misura. Ed è esattamente quello che vedremo in questa guida — dai concetti base fino al morphing avanzato, con codice pronto da copiare nei vostri progetti.

Cos'è il Liquid Glass e Come Funziona

In parole semplici, il Liquid Glass è un materiale adattivo che crea gerarchia visiva nelle app. Il contenuto sta in basso, i controlli di navigazione fluttuano sopra come lastre di vetro liquido. Fin qui, niente di nuovo.

Ma ecco dove si fa interessante: la differenza rispetto ai vecchi effetti di sfocatura (.ultraThinMaterial, .regularMaterial) è tutta nel meccanismo ottico. Il blur tradizionale diffonde la luce. Il Liquid Glass la concentra e piega attraverso il lensing, un po' come un prisma di vetro vero. L'effetto visivo è su un altro livello, onestamente.

Adozione Automatica: Cosa Cambia Senza Scrivere Codice

Quando ricompilate la vostra app con Xcode 26, questi elementi adottano il Liquid Glass in automatico:

  • NavigationBar e TabBar
  • Toolbar e relativi elementi
  • Sheet, Popover e Menu
  • Alert e finestre di dialogo
  • Toggle, Slider e Picker

Zero righe di codice da modificare per questi componenti. Però le viste personalizzate richiedono un intervento esplicito — ed è proprio qui che le cose si fanno divertenti.

Il Modifier .glassEffect(): Le Basi

Ok, partiamo dal cuore dell'API. Il modifier .glassEffect() ha questa firma:

func glassEffect<S: Shape>(
    _ glass: Glass = .regular,
    in shape: S = DefaultGlassEffectShape,
    isEnabled: Bool = true
) -> some View

Nella forma più semplice, basta aggiungerlo a qualsiasi vista:

import SwiftUI

struct VistaBaseGlass: View {
    var body: some View {
        Text("Ciao, Liquid Glass!")
            .padding()
            .glassEffect()
    }
}

Di default, il modifier applica la variante .regular con forma a capsula. SwiftUI usa automaticamente un colore di testo vibrante che si adatta per restare leggibile su sfondi colorati — una di quelle cose che "funziona e basta".

Specificare una Forma Personalizzata

La capsula non fa al caso vostro? Nessun problema, potete specificare qualsiasi forma SwiftUI:

import SwiftUI

struct FormeGlass: View {
    var body: some View {
        VStack(spacing: 20) {
            // Rettangolo arrotondato
            Text("Rettangolo")
                .padding()
                .glassEffect(in: .rect(cornerRadius: 16))

            // Cerchio
            Image(systemName: "star.fill")
                .font(.title)
                .frame(width: 60, height: 60)
                .glassEffect(in: .circle)

            // Capsula (default)
            Text("Capsula")
                .padding(.horizontal, 24)
                .padding(.vertical, 12)
                .glassEffect(in: .capsule)
        }
    }
}

Le Varianti: .regular, .clear e .identity

Il tipo Glass offre tre varianti, ognuna con il suo ruolo preciso:

  • .regular — La variante predefinita, bilanciata e leggibile. È quella che userete nel 90% dei casi, fidatevi.
  • .clear — Più trasparente e visivamente ricca. Da usare solo quando lo sfondo è pieno di contenuti media (foto, video) e gli elementi sovrapposti sono grandi e ben distinguibili.
  • .identity — Rimuove completamente l'effetto glass. Utile per fallback accessibili o per disabilitare il glass in modo condizionale.
import SwiftUI

struct VariantiGlass: View {
    var body: some View {
        ZStack {
            // Sfondo ricco di media
            Image("panorama")
                .resizable()
                .scaledToFill()
                .ignoresSafeArea()

            VStack(spacing: 20) {
                Text("Regular Glass")
                    .padding()
                    .glassEffect(.regular)

                Text("Clear Glass")
                    .padding()
                    .glassEffect(.clear)
            }
        }
    }
}

Regola importante: non mescolate mai .regular e .clear nello stesso contesto visivo. Le due varianti hanno proprietà ottiche diverse e accostarle crea un effetto incoerente (l'ho provato personalmente, non è un bel vedere).

Tinta e Interattività

Il Liquid Glass si può personalizzare con tinte colorate e comportamenti interattivi. Vediamo come.

Aggiungere una Tinta

Qui c'è un punto su cui Apple insiste parecchio: la tinta va usata con moderazione. L'idea è colorare solo l'azione primaria per creare gerarchia visiva:

import SwiftUI

struct TintaGlass: View {
    var body: some View {
        VStack(spacing: 16) {
            // Tinta piena
            Button("Azione Principale") { }
                .padding()
                .glassEffect(.regular.tint(.blue))

            // Tinta con opacità ridotta
            Button("Azione Secondaria") { }
                .padding()
                .glassEffect(.regular.tint(.purple.opacity(0.6)))

            // Senza tinta - per azioni terziarie
            Button("Altra Azione") { }
                .padding()
                .glassEffect()
        }
    }
}

La tinta usa un colore vibrante che si adatta al contenuto retrostante. Se tinteggiate più elementi contemporaneamente, la gerarchia si appiattisce — e a quel punto, a che serve averla?

Il Modifier .interactive()

Su iOS, .interactive() aggiunge effetti di risposta al tocco: scaling, rimbalzo e un sottile shimmer nel punto di contatto. Rende tutto più... vivo.

import SwiftUI

struct BottoneInterattivo: View {
    var body: some View {
        Button {
            // azione
        } label: {
            HStack {
                Image(systemName: "plus")
                Text("Nuovo Elemento")
            }
            .padding()
        }
        .glassEffect(.regular.tint(.blue).interactive())
    }
}

Un avvertimento: .interactive() pesa sulla GPU. Applicatelo solo dove conta davvero — pulsanti principali, elementi hero, punti focali. I bottoni creati con .buttonStyle(.glass) includono già questi effetti, quindi non c'è bisogno di aggiungerlo manualmente in quel caso.

Stili Bottone Integrati

SwiftUI mette a disposizione due stili bottone dedicati al Liquid Glass che vi semplificheranno la vita:

import SwiftUI

struct StiliBottone: View {
    var body: some View {
        HStack(spacing: 16) {
            // Stile secondario - traslucido
            Button("Annulla") { }
                .buttonStyle(.glass)

            // Stile primario - prominente con tinta
            Button("Salva") { }
                .buttonStyle(.glassProminent)
                .tint(.blue)
        }
        .padding()
    }
}

.glass per le azioni secondarie, .glassProminent per l'azione primaria. Gerarchia immediata senza dover giocare con colori o dimensioni diverse.

GlassEffectContainer: Raggruppare gli Elementi

Questo è un concetto che molti sottovalutano. Quando avete più elementi glass nella stessa area, dovete raggrupparli in un GlassEffectContainer. Il motivo è tecnico ma fondamentale: il glass non può campionare altro glass. Senza container, ogni elemento campiona lo sfondo per conto suo, e il risultato visivo è... discutibile.

import SwiftUI

struct ToolbarPersonalizzata: View {
    var body: some View {
        // ✅ Corretto: container condiviso
        GlassEffectContainer {
            HStack(spacing: 16) {
                Button("Modifica") { }
                    .glassEffect()

                Button("Condividi") { }
                    .glassEffect()

                Button("Elimina") { }
                    .glassEffect(.regular.tint(.red))
            }
            .padding()
        }
    }
}

Confrontatelo con l'approccio sbagliato:

import SwiftUI

struct ToolbarSbagliata: View {
    var body: some View {
        // ❌ Sbagliato: nessun container
        HStack(spacing: 16) {
            Button("Modifica") { }
                .glassEffect()

            Button("Condividi") { }
                .glassEffect()

            Button("Elimina") { }
                .glassEffect(.regular.tint(.red))
        }
        .padding()
    }
}

Tre vantaggi concreti del GlassEffectContainer:

  1. Render unificato — Un singolo passaggio di rendering per tutti gli elementi. Performance migliori, specialmente con molti bottoni.
  2. Aspetto coerente — Tutti gli elementi condividono la stessa regione di campionamento.
  3. Morphing fluido — Gli elementi vicini si fondono visivamente in un'unica superficie glass, creando quell'effetto di continuità che rende l'interfaccia davvero curata.

Il Parametro spacing

Il parametro spacing controlla la soglia di morphing — in pratica, quanto vicini devono essere gli elementi prima di fondersi visivamente:

import SwiftUI

struct ContainerConSpacing: View {
    var body: some View {
        // Elementi entro 30pt si fondono
        GlassEffectContainer(spacing: 30) {
            HStack(spacing: 20) {
                Button("A") { }
                    .glassEffect()
                Button("B") { }
                    .glassEffect()
                Button("C") { }
                    .glassEffect()
            }
        }
    }
}

Morphing e Animazioni con glassEffectID

Arriviamo alla parte più bella (almeno per me). Il Liquid Glass permette di creare transizioni fluide tra elementi glass, e il risultato è davvero spettacolare. Servono tre ingredienti:

  1. Gli elementi devono stare dentro un GlassEffectContainer
  2. Ogni elemento deve avere un glassEffectID unico con un namespace condiviso
  3. I cambiamenti di stato vanno avvolti in un'animazione
import SwiftUI

struct BarraEspandibile: View {
    @State private var espanso = false
    @Namespace private var namespace

    var body: some View {
        GlassEffectContainer(spacing: 20) {
            HStack(spacing: 16) {
                if espanso {
                    Button {
                        // condividi
                    } label: {
                        Image(systemName: "square.and.arrow.up")
                            .font(.title2)
                            .frame(width: 50, height: 50)
                    }
                    .glassEffect(.regular.interactive())
                    .glassEffectID("condividi", in: namespace)

                    Button {
                        // fotocamera
                    } label: {
                        Image(systemName: "camera")
                            .font(.title2)
                            .frame(width: 50, height: 50)
                    }
                    .glassEffect(.regular.interactive())
                    .glassEffectID("fotocamera", in: namespace)

                    Button {
                        // preferiti
                    } label: {
                        Image(systemName: "heart")
                            .font(.title2)
                            .frame(width: 50, height: 50)
                    }
                    .glassEffect(.regular.interactive())
                    .glassEffectID("preferiti", in: namespace)
                }

                Button {
                    withAnimation(.bouncy) {
                        espanso.toggle()
                    }
                } label: {
                    Image(systemName: espanso ? "xmark" : "plus")
                        .font(.title2)
                        .frame(width: 50, height: 50)
                }
                .glassEffect(
                    .regular.tint(.blue).interactive()
                )
                .glassEffectID("toggle", in: namespace)
            }
        }
    }
}

Quando l'utente tocca il "+" , i pulsanti aggiuntivi appaiono con un'animazione di morphing: il glass si espande, si divide e si ricompone in modo organico. L'effetto dà un senso di fisicità all'interfaccia che prima era impossibile ottenere con SwiftUI.

Errori Comuni da Evitare

Apple è stata piuttosto esplicita su come non usare il Liquid Glass. Vi risparmio i tentativi a vuoto che ho fatto io e andiamo dritti al punto.

1. Non Applicare il Glass al Contenuto

Il Liquid Glass è pensato esclusivamente per il livello di navigazione. Mai applicarlo a righe di lista, card o elementi scrollabili. Sembra un'idea carina, ma il risultato è sempre confuso.

import SwiftUI

struct EsempioSbagliato: View {
    let elementi = ["Mela", "Banana", "Ciliegia", "Dattero"]

    var body: some View {
        // ❌ SBAGLIATO - Glass sul contenuto
        List(elementi, id: \.self) { elemento in
            Text(elemento)
                .padding()
                .glassEffect() // Non fatelo!
        }
    }
}

struct EsempioCorretto: View {
    let elementi = ["Mela", "Banana", "Ciliegia", "Dattero"]

    var body: some View {
        // ✅ CORRETTO - Glass solo sui controlli fluttuanti
        ZStack(alignment: .bottom) {
            List(elementi, id: \.self) { elemento in
                Text(elemento)
            }

            Button {
                // aggiungi elemento
            } label: {
                Image(systemName: "plus")
                    .font(.title2)
                    .frame(width: 56, height: 56)
            }
            .glassEffect(.regular.tint(.blue).interactive())
            .padding(.bottom, 20)
        }
    }
}

2. Non Impilare Glass su Glass

Impilare elementi Liquid Glass l'uno sull'altro rende l'interfaccia pesante e confusa. Il glass non riesce a campionare correttamente altro glass, quindi il risultato è sempre deludente. Resistete alla tentazione.

3. Non Dimenticare di Rimuovere i Background

Questo è un errore subdolo. Se applicate .glassEffect() a una vista che ha già un .background(), l'effetto glass sparisce. Il background opaco lo copre completamente:

import SwiftUI

struct BackgroundConflict: View {
    var body: some View {
        VStack(spacing: 20) {
            // ❌ Il background blocca il glass
            Text("Non si vede")
                .padding()
                .background(Circle().fill(.purple))
                .glassEffect()

            // ✅ Usare la tinta al posto del background
            Text("Perfetto")
                .padding()
                .glassEffect(.regular.tint(.purple.opacity(0.8)))
        }
    }
}

4. Non Tinteggiare Troppi Elementi

Se colorate tutto, non evidenziate nulla. Semplice. Limitate la tinta all'azione primaria e lasciate gli altri elementi con il semplice .regular.

Compatibilità con Versioni Precedenti di iOS

Realisticamente, la maggior parte delle app deve ancora supportare iOS 18 (o anche prima). Le API del Liquid Glass non esistono lì, quindi serve un fallback. Ecco un approccio pulito:

import SwiftUI

struct BottoneCompatibile: View {
    var body: some View {
        Button("Azione") {
            // logica
        }
        .padding()
        .modifier(GlassCompatibile())
    }
}

struct GlassCompatibile: ViewModifier {
    func body(content: Content) -> some View {
        if #available(iOS 26.0, *) {
            content
                .glassEffect(.regular.interactive())
        } else {
            content
                .background(.ultraThinMaterial)
                .clipShape(Capsule())
        }
    }
}

Con #available applicate il Liquid Glass su iOS 26 e degradate a .ultraThinMaterial sulle versioni precedenti. L'esperienza utente resta coerente, anche se non identica. Un compromesso più che accettabile.

Ottimizzazione delle Performance

Ogni layer glass ha un costo computazionale. Non è enorme, ma si accumula. Ecco le regole da seguire:

  • Usate sempre GlassEffectContainer — Unifica il rendering in un singolo passaggio. Senza container, ogni elemento viene elaborato separatamente e la GPU piange.
  • Limitate .interactive() — L'effetto tocco è bello ma pesa. Riservatelo agli elementi che l'utente tocca davvero.
  • Niente glass sulle liste — Oltre a essere un errore di design (come abbiamo visto), applicare il glass a decine di righe è un disastro per le performance.
  • Testate su dispositivi reali — Il simulatore non riflette le performance reali del Liquid Glass. I dispositivi più vecchi compatibili (come iPhone 15) potrebbero mostrare rallentamenti con un uso eccessivo.

Accessibilità: Zero Lavoro Extra

Una delle cose che apprezzo di più di questa implementazione è che l'accessibilità funziona da sola. Il sistema gestisce tutto automaticamente:

  • Riduci Trasparenza attivato → Il glass diventa più opaco e "brinato"
  • Aumenta Contrasto attivato → Il glass passa a bianco/nero con bordo definito
  • Riduci Movimento attivato → Effetti elastici e shimmer vengono disabilitati

Se per qualche motivo avete bisogno di un fallback manuale, potete leggere la variabile d'ambiente:

import SwiftUI

struct VistaAccessibile: View {
    @Environment(\.accessibilityReduceTransparency) var riduciTrasparenza

    var body: some View {
        Text("Contenuto")
            .padding()
            .glassEffect(riduciTrasparenza ? .identity : .regular)
    }
}

Nella maggior parte dei casi, però, lasciate fare al sistema. Apple ha pensato bene a questo aspetto.

Esempio Pratico: Mini Player Musicale

Mettiamo tutto insieme con qualcosa di concreto — un mini-player musicale con controlli in Liquid Glass:

import SwiftUI

struct MiniPlayer: View {
    @State private var inRiproduzione = false
    @State private var mostraControlli = false
    @Namespace private var namespace

    var body: some View {
        ZStack(alignment: .bottom) {
            // Contenuto dell'app (sfondo)
            LinearGradient(
                colors: [.indigo, .purple, .pink],
                startPoint: .topLeading,
                endPoint: .bottomTrailing
            )
            .ignoresSafeArea()

            VStack {
                Spacer()

                // Copertina album
                Image(systemName: "music.note")
                    .font(.system(size: 120))
                    .foregroundStyle(.white.opacity(0.3))

                Spacer()

                // Mini player con Liquid Glass
                GlassEffectContainer(spacing: 12) {
                    VStack(spacing: 16) {
                        // Info brano
                        HStack {
                            Image(systemName: "music.note.list")
                                .frame(width: 44, height: 44)
                                .glassEffect(in: .rect(cornerRadius: 8))
                                .glassEffectID("copertina", in: namespace)

                            VStack(alignment: .leading) {
                                Text("Titolo del Brano")
                                    .font(.headline)
                                Text("Nome Artista")
                                    .font(.subheadline)
                                    .foregroundStyle(.secondary)
                            }

                            Spacer()
                        }
                        .padding(.horizontal)

                        // Controlli di riproduzione
                        HStack(spacing: 24) {
                            Button {
                                // brano precedente
                            } label: {
                                Image(systemName: "backward.fill")
                                    .font(.title2)
                                    .frame(width: 50, height: 50)
                            }
                            .glassEffect(.regular.interactive())
                            .glassEffectID("precedente", in: namespace)

                            Button {
                                withAnimation(.bouncy) {
                                    inRiproduzione.toggle()
                                }
                            } label: {
                                Image(systemName: inRiproduzione
                                    ? "pause.fill" : "play.fill")
                                    .font(.title)
                                    .frame(width: 60, height: 60)
                            }
                            .glassEffect(
                                .regular
                                    .tint(.blue)
                                    .interactive()
                            )
                            .glassEffectID("riproduci", in: namespace)

                            Button {
                                // brano successivo
                            } label: {
                                Image(systemName: "forward.fill")
                                    .font(.title2)
                                    .frame(width: 50, height: 50)
                            }
                            .glassEffect(.regular.interactive())
                            .glassEffectID("successivo", in: namespace)
                        }
                    }
                    .padding()
                }
                .padding(.horizontal)
                .padding(.bottom, 30)
            }
        }
    }
}

In questo esempio, il GlassEffectContainer raggruppa tutti i controlli. Il bottone play/pausa è l'unico con tinta blu — gerarchia visiva chiara e immediata. I glassEffectID abilitano transizioni fluide quando lo stato cambia, e .interactive() rende il feedback al tocco naturale e appagante.

Domande Frequenti

Come si applica il Liquid Glass a una vista personalizzata in SwiftUI?

Aggiungete il modifier .glassEffect() alla vista. Per personalizzare, specificate una variante (.regular o .clear), una forma (.capsule, .circle, .rect(cornerRadius:)), una tinta con .tint(.colore) e l'interattività con .interactive(). Ricordatevi di rimuovere eventuali .background() preesistenti, altrimenti il glass non si vedrà.

Il Liquid Glass funziona su versioni di iOS precedenti alla 26?

No. Le API (.glassEffect(), GlassEffectContainer, .buttonStyle(.glass)) esistono solo da iOS 26 in poi. Per supportare versioni precedenti, usate #available(iOS 26.0, *) per applicare il glass condizionalmente e fornite un fallback con .ultraThinMaterial.

Qual è la differenza tra .glassEffect() e .buttonStyle(.glass)?

Il modifier .glassEffect() è più flessibile: va su qualsiasi vista e supporta tinta, forma personalizzata, interattività e morphing con glassEffectID. Lo stile .buttonStyle(.glass) è specifico per i bottoni, più rapido da usare ma con meno opzioni. Per la massima flessibilità, andate con .glassEffect().

Come si evitano problemi di performance con il Liquid Glass?

Tre regole: raggruppate gli elementi in un GlassEffectContainer, limitate .interactive() agli elementi realmente interattivi, e non applicate mai il glass a liste scrollabili. E testate su dispositivi reali — il simulatore mente sulle performance.

È possibile disattivare il Liquid Glass nella propria app?

Sì, Apple ha previsto un opt-out per le app non ancora pronte. Però sarà disponibile solo fino al prossimo major release (probabilmente iOS 27). Il consiglio è iniziare ad adattare l'app il prima possibile — tanto, prima o poi, dovrete farlo comunque.

Sull'Autore Editorial Team

Our team of expert writers and editors.