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:
- Render unificato — Un singolo passaggio di rendering per tutti gli elementi. Performance migliori, specialmente con molti bottoni.
- Aspetto coerente — Tutti gli elementi condividono la stessa regione di campionamento.
- 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:
- Gli elementi devono stare dentro un
GlassEffectContainer - Ogni elemento deve avere un
glassEffectIDunico con un namespace condiviso - 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.