Liquid Glass dans SwiftUI : le guide pratique pour iOS 26

Découvrez le Liquid Glass d'iOS 26 en SwiftUI. Ce guide pratique couvre les API glassEffect(), GlassEffectContainer et glassEffectID() avec des exemples concrets, des conseils de migration et les pièges à éviter.

Introduction : bienvenue dans l'ère du Liquid Glass

Si vous suivez l'écosystème Apple, vous le savez : la firme de Cupertino ne fait jamais les choses à moitié quand il s'agit de design. Avec iOS 26, annoncé à la WWDC 2025, Apple a dévoilé sa plus grande révolution visuelle depuis iOS 7 — le Liquid Glass. Ce nouveau langage de design, fondé sur un matériau translucide et dynamique, transforme radicalement l'apparence des interfaces sur toutes les plateformes Apple : iOS, iPadOS, macOS, watchOS et tvOS.

Honnêtement, quand j'ai vu la keynote pour la première fois, j'ai eu un moment de « wow, ça change tout ». Et après avoir passé pas mal de temps à expérimenter avec les API, je peux confirmer que c'est bien plus qu'un simple lifting cosmétique.

Le Liquid Glass vient avec tout un ensemble d'API SwiftUI dédiées : glassEffect(), GlassEffectContainer, glassEffectID(), et d'autres encore. Ces outils permettent de créer des interfaces modernes avec des effets de transparence adaptative, des animations de morphing fluides et des interactions tactiles vraiment immersives.

Allez, on plonge dans le vif du sujet. Dans ce guide, on va explorer chaque aspect du Liquid Glass dans SwiftUI — de la philosophie de design aux exemples de code concrets, en passant par les pièges à éviter et les bonnes pratiques de performance.

La philosophie du Liquid Glass : comprendre avant de coder

Avant de se jeter sur le code, prenons un moment pour comprendre la philosophie derrière le Liquid Glass. Croyez-moi, ça vous évitera pas mal d'erreurs frustrantes par la suite.

Un matériau pour la navigation, pas pour le contenu

C'est LA règle d'or du Liquid Glass : il est exclusivement destiné à la couche de navigation — celle qui flotte au-dessus du contenu de votre app. Il ne doit jamais être appliqué au contenu principal (listes, tableaux, médias, textes).

  • Utilisation appropriée : barres d'outils, onglets, boutons d'action flottants, contrôles de navigation, overlays
  • Utilisation inappropriée : cellules de liste, cartes de contenu, arrière-plans de page, zones de texte principales

Pourquoi cette restriction ? Le Liquid Glass fonctionne en réfractant et reflétant le contenu situé en dessous. Si vous l'appliquez au contenu lui-même, vous obtenez un effet de « verre sur du verre » qui n'est pas joli du tout — et qui pose aussi des problèmes de lisibilité et de performance.

Transparence adaptative et profondeur

Le Liquid Glass, ce n'est pas un simple filtre de flou. C'est un matériau dynamique qui s'adapte en temps réel à son environnement :

  • Réfraction lumineuse (lensing) : le contenu derrière le verre est subtilement déformé, ce qui crée une vraie impression de profondeur physique
  • Reflets spéculaires : des points lumineux réagissent au mouvement de l'appareil grâce aux capteurs de mouvement
  • Ombres adaptatives : les ombres portées s'ajustent automatiquement selon le contexte
  • Comportements interactifs : le matériau répond aux touches avec des effets de rebond et de miroitement

Le résultat est bluffant. On a vraiment l'impression de manipuler un objet physique.

Divulgation progressive du design

Tout comme Swift 6.2 adopte la divulgation progressive pour la concurrence, le Liquid Glass suit le même principe côté design. Les composants SwiftUI standard — TabView, NavigationStack, les barres d'outils — adoptent automatiquement le Liquid Glass dans iOS 26, sans aucune modification de code. C'est seulement quand vous créez des interfaces personnalisées que vous devez utiliser les API explicitement.

Plutôt malin comme approche, non ?

Les API fondamentales : glassEffect() et ses variantes

Bon, passons aux choses sérieuses. SwiftUI introduit plusieurs modificateurs et types pour travailler avec le Liquid Glass. Commençons par le plus fondamental.

Le modificateur glassEffect()

Le modificateur glassEffect() est votre point d'entrée principal pour appliquer un effet de verre liquide à n'importe quelle vue SwiftUI. Dans sa forme la plus simple, une seule ligne suffit :

Button {
    // Action
} label: {
    Label("Accueil", systemImage: "house")
}
.glassEffect()

Ce simple appel transforme le bouton en un élément Liquid Glass avec des coins arrondis automatiques, un flou adaptatif et une transparence dynamique. SwiftUI gère tout le rendu sous-jacent pour vous.

Les variantes de style Glass

Le modificateur glassEffect() accepte un paramètre de style qui définit le comportement visuel du verre. Trois variantes principales sont disponibles :

// Variante régulière — polyvalente et adaptative
Text("Standard")
    .padding()
    .glassEffect(.regular)

// Variante claire — toujours transparente
Text("Transparent")
    .padding()
    .glassEffect(.clear)

// Variante identité — pas d'effet de verre (utile pour l'accessibilité)
Text("Sans effet")
    .padding()
    .glassEffect(.identity)

La variante .regular est celle que vous utiliserez le plus souvent — elle convient à la grande majorité des cas. La variante .clear reste permanemment transparente, tandis que .identity désactive complètement l'effet. Cette dernière est particulièrement utile dans un contexte d'accessibilité (on en reparlera plus bas).

La teinte (tint) : ajouter de la couleur au verre

Vous pouvez colorer le Liquid Glass avec une teinte pour indiquer l'état ou l'identité visuelle d'un élément :

// Bouton avec teinte violette
Button("Modifier") { }
    .glassEffect(.regular.tint(.purple.opacity(0.8)))

// Bouton avec teinte orange
Button("Supprimer") { }
    .glassEffect(.regular.tint(.orange))

// Teinte bleue semi-transparente
Button("Partager") { }
    .glassEffect(.regular.tint(.blue.opacity(0.5)))

La teinte se mélange subtilement avec le matériau de verre, ce qui crée un effet coloré tout en restant transparent et lumineux. Avec opacity(), vous pouvez doser précisément l'intensité de la coloration.

Les interactions tactiles avec .interactive()

C'est probablement l'un des aspects les plus cool du Liquid Glass : sa capacité à réagir aux interactions de l'utilisateur. Le modificateur .interactive() ajoute des retours visuels riches lors des touchers :

// Bouton avec effet interactif
Image(systemName: "pencil")
    .frame(width: 44, height: 44)
    .glassEffect(.regular.interactive())

// Combinaison teinte + interactif
Image(systemName: "trash")
    .frame(width: 44, height: 44)
    .glassEffect(.regular.tint(.red.opacity(0.7)).interactive())

Quand .interactive() est activé, le bouton répond aux touches avec un effet de mise à l'échelle, un rebond élastique et un miroitement lumineux. Ça donne une dimension physique à l'interaction — et vos utilisateurs vont adorer.

Les styles de bouton Glass

iOS 26 introduit aussi deux nouveaux styles de bouton dédiés au Liquid Glass, comme alternative au modificateur glassEffect() :

// Style glass — translucide, pour les actions secondaires
Button("Annuler") { }
    .buttonStyle(.glass)

// Style glass proéminent — opaque, pour les actions principales
Button("Valider") { }
    .buttonStyle(.glassProminent)

La distinction entre .glass et .glassProminent est importante pour la hiérarchie visuelle : le premier pour les actions secondaires, le second pour les actions primaires qui doivent attirer l'attention de l'utilisateur.

GlassEffectContainer : le conteneur pour les effets groupés

Le GlassEffectContainer est un composant central du système Liquid Glass. Il regroupe plusieurs éléments de verre au sein d'un même conteneur, ce qui active deux comportements essentiels : le morphing visuel entre éléments proches et l'optimisation du rendu.

Fonctionnement de base

struct ToolbarVerre: View {
    var body: some View {
        GlassEffectContainer {
            HStack(spacing: 16) {
                Button {
                    // Action accueil
                } label: {
                    Image(systemName: "house")
                }
                .glassEffect()

                Button {
                    // Action recherche
                } label: {
                    Image(systemName: "magnifyingglass")
                }
                .glassEffect()

                Button {
                    // Action paramètres
                } label: {
                    Image(systemName: "gearshape")
                }
                .glassEffect()
            }
            .padding()
        }
    }
}

Quand les trois boutons sont à l'intérieur d'un GlassEffectContainer et suffisamment proches, SwiftUI fusionne visuellement leurs effets de verre en une seule forme cohérente. Apple appelle ça le « morphing » — les éléments semblent naître d'un même morceau de verre liquide. L'effet est franchement réussi.

Le paramètre spacing : contrôler le seuil de fusion

Le paramètre spacing du conteneur détermine la distance maximale à laquelle deux éléments vont fusionner visuellement :

// Fusion rapprochée — les éléments doivent être très proches
GlassEffectContainer(spacing: 10) {
    HStack(spacing: 8) {
        boutonAction(icone: "pencil")
        boutonAction(icone: "eraser")
        boutonAction(icone: "paintbrush")
    }
}

// Fusion élargie — les éléments fusionnent à plus grande distance
GlassEffectContainer(spacing: 40) {
    HStack(spacing: 30) {
        boutonAction(icone: "pencil")
        boutonAction(icone: "eraser")
        boutonAction(icone: "paintbrush")
    }
}

Trouver la bonne valeur de spacing demande un peu de tâtonnement. Trop faible, et les éléments paraissent déconnectés. Trop élevé, et tout se confond en une masse indistincte. Mon conseil : commencez avec la valeur par défaut et ajustez progressivement jusqu'à obtenir le résultat souhaité.

Optimisation du rendu par le conteneur

Au-delà de l'esthétique, le GlassEffectContainer a un rôle crucial côté performance. Sous le capot, chaque effet de verre nécessite un CABackdropLayer qui capture le contenu en arrière-plan. Et chaque couche de backdrop requiert trois textures hors écran — c'est gourmand en GPU.

En regroupant les effets dans un conteneur, SwiftUI consolide tout ça en une seule couche de backdrop partagée. Résultat : moins de passes de rendu, moins de consommation GPU, et des animations plus fluides. C'est particulièrement important sur les appareils un peu plus anciens.

Morphing et transitions avec glassEffectID()

Le glassEffectID() est, à mon avis, la fonctionnalité la plus spectaculaire du Liquid Glass dans SwiftUI. Il permet de créer des animations de morphing fluides entre des éléments de verre qui apparaissent ou disparaissent — donnant l'impression que les éléments se transforment littéralement les uns en les autres.

Les trois ingrédients du morphing

Pour créer une animation de morphing, il vous faut trois éléments :

  1. Un GlassEffectContainer — le conteneur qui héberge toutes les vues animées
  2. Un @Namespace — une propriété qui fournit le contexte d'animation partagé
  3. Un rendu conditionnel — l'affichage ou le masquage des vues pour déclencher les transitions

Exemple complet : menu d'actions extensible

Voici un exemple concret d'un bouton qui se transforme en menu de plusieurs actions :

struct MenuExtensible: View {
    @State private var estEtendu = false
    @Namespace private var espaceNom

    var body: some View {
        ZStack {
            // Contenu en arrière-plan
            Image("fond-paysage")
                .resizable()
                .scaledToFill()
                .ignoresSafeArea()

            VStack {
                Spacer()

                GlassEffectContainer(spacing: 30) {
                    VStack(spacing: 12) {
                        if estEtendu {
                            Button {
                                // Action de rotation
                            } label: {
                                Image(systemName: "rotate.right")
                                    .frame(width: 44, height: 44)
                            }
                            .glassEffect(.regular.interactive())
                            .glassEffectID("rotation", in: espaceNom)

                            Button {
                                // Action de partage
                            } label: {
                                Image(systemName: "square.and.arrow.up")
                                    .frame(width: 44, height: 44)
                            }
                            .glassEffect(.regular.interactive())
                            .glassEffectID("partage", in: espaceNom)

                            Button {
                                // Action de téléchargement
                            } label: {
                                Image(systemName: "arrow.down.circle")
                                    .frame(width: 44, height: 44)
                            }
                            .glassEffect(.regular.interactive())
                            .glassEffectID("telechargement", in: espaceNom)
                        }

                        // Bouton principal de basculement
                        Button {
                            withAnimation(.bouncy(duration: 0.4)) {
                                estEtendu.toggle()
                            }
                        } label: {
                            Image(systemName: estEtendu ? "xmark" : "plus")
                                .frame(width: 50, height: 50)
                                .font(.title2)
                        }
                        .glassEffect(.regular.tint(.blue.opacity(0.6)).interactive())
                        .glassEffectID("basculer", in: espaceNom)
                    }
                }
                .padding(.bottom, 40)
            }
        }
    }
}

Quand l'utilisateur touche le bouton « + », trois boutons d'action apparaissent avec une animation de morphing — ils semblent émerger du bouton principal comme du verre liquide qui se divise. Et quand on referme, ils se résorbent dans le bouton principal. L'effet est vraiment saisissant.

Points critiques pour le morphing

Chaque vue qui participe au morphing doit avoir un glassEffectID unique au sein du namespace partagé. Sans cet identifiant, la vue apparaîtra ou disparaîtra brutalement — pas de transformation fluide.

Pensez aussi à toujours envelopper le changement d'état dans un withAnimation(). Sans ça, pas d'animation du tout.

L'animation .bouncy est particulièrement recommandée pour le morphing du Liquid Glass, car elle reproduit la physique d'un matériau élastique. Mais rien ne vous empêche d'expérimenter avec d'autres courbes :

// Animation rebondissante (recommandée)
withAnimation(.bouncy(duration: 0.4)) {
    estEtendu.toggle()
}

// Animation fluide et rapide
withAnimation(.smooth(duration: 0.3)) {
    estEtendu.toggle()
}

// Animation avec ressort personnalisé
withAnimation(.spring(response: 0.5, dampingFraction: 0.7)) {
    estEtendu.toggle()
}

Construire une barre d'outils personnalisée en Liquid Glass

Maintenant qu'on maîtrise les briques de base, mettons tout ensemble pour construire quelque chose de concret : une barre d'outils flottante personnalisée avec des effets Liquid Glass.

struct BarreOutilsFlottante: View {
    @State private var outilSelectionne: String = "pinceau"
    @Namespace private var espaceNom

    let outils: [(id: String, icone: String, label: String)] = [
        ("pinceau", "paintbrush.pointed", "Pinceau"),
        ("crayon", "pencil", "Crayon"),
        ("gomme", "eraser", "Gomme"),
        ("pipette", "eyedropper", "Pipette"),
        ("forme", "square.on.circle", "Formes")
    ]

    var body: some View {
        GlassEffectContainer(spacing: 20) {
            HStack(spacing: 12) {
                ForEach(outils, id: \.id) { outil in
                    Button {
                        withAnimation(.bouncy(duration: 0.3)) {
                            outilSelectionne = outil.id
                        }
                    } label: {
                        VStack(spacing: 4) {
                            Image(systemName: outil.icone)
                                .font(.title3)
                            if outilSelectionne == outil.id {
                                Text(outil.label)
                                    .font(.caption2)
                            }
                        }
                        .frame(width: outilSelectionne == outil.id ? 60 : 44,
                               height: 44)
                    }
                    .glassEffect(
                        outilSelectionne == outil.id
                            ? .regular.tint(.blue.opacity(0.6)).interactive()
                            : .regular.interactive()
                    )
                    .glassEffectID(outil.id, in: espaceNom)
                }
            }
            .padding(.horizontal, 8)
            .padding(.vertical, 6)
        }
    }
}

Cette barre d'outils combine plusieurs techniques : le morphing entre états (l'outil sélectionné s'agrandit), la teinte pour indiquer la sélection, les interactions tactiles, et le regroupement dans un conteneur pour optimiser le rendu. C'est le genre de composant qui impressionne dans une app de dessin ou d'édition.

La fusion de verre avec glassEffectUnion()

Parfois, on veut que des vues réparties dans différentes parties de la hiérarchie SwiftUI partagent le même effet de verre, sans forcément les imbriquer dans le même GlassEffectContainer. C'est exactement le rôle de glassEffectUnion().

struct InterfaceAvecUnion: View {
    var body: some View {
        GlassEffectContainer {
            VStack(spacing: 20) {
                // Groupe du haut
                HStack {
                    Text("Titre de la section")
                        .font(.headline)
                    Spacer()
                    Button(action: {}) {
                        Image(systemName: "ellipsis")
                    }
                    .glassEffect()
                }
                .padding()
                .glassEffectUnion()

                // Groupe du bas, visuellement fusionné
                HStack(spacing: 8) {
                    Button("Filtre 1") { }
                        .glassEffect()
                    Button("Filtre 2") { }
                        .glassEffect()
                    Button("Filtre 3") { }
                        .glassEffect()
                }
                .glassEffectUnion()
            }
        }
    }
}

Le modificateur glassEffectUnion() permet de regrouper visuellement des vues ayant des effets similaires, sans ajouter de conteneurs de mise en page supplémentaires. Super pratique quand votre hiérarchie de vues est déjà un peu complexe.

Accessibilité et Liquid Glass : ne l'oubliez pas

Le Liquid Glass, c'est magnifique. Mais ce n'est pas le cas pour tout le monde. Certains utilisateurs ont besoin de réduire la transparence pour des raisons de lisibilité ou de confort visuel — et c'est notre responsabilité de développeur de respecter ça.

Respecter accessibilityReduceTransparency

struct BoutonAccessible: View {
    @Environment(\.accessibilityReduceTransparency)
    private var reduireTransparence

    var body: some View {
        Button("Action") { }
            .padding()
            .glassEffect(
                reduireTransparence ? .identity : .regular
            )
    }
}

Quand l'utilisateur a activé « Réduire la transparence » dans les réglages d'accessibilité, on bascule vers la variante .identity qui désactive l'effet de verre au profit d'un fond opaque. C'est une bonne pratique que vous devriez appliquer systématiquement dans vos vues personnalisées.

Taille des zones interactives

Un piège courant (et assez vicieux) : les boutons avec un effet de verre ne répondent aux touches que sur la zone de leur label, pas sur l'intégralité de la forme visible. Pour corriger ça, utilisez contentShape() :

Button {
    // Action
} label: {
    Image(systemName: "pencil")
        .frame(width: 44, height: 44)
}
.contentShape(Circle())
.glassEffect(.regular.interactive())

Le modificateur contentShape(Circle()) étend la zone interactive pour correspondre à la forme visuelle du bouton. Sans ça, l'utilisateur peut toucher une zone qui semble « dans le bouton » sans que rien ne se passe. Frustrant.

Pièges courants et solutions pratiques

Le Liquid Glass, malgré sa simplicité apparente, réserve quelques surprises. Voici les pièges dans lesquels j'ai vu (et parfois mis) le pied — et comment les éviter.

Piège n°1 : la rotation et la déformation du verre

Quand vous appliquez rotationEffect() à une vue avec un effet de verre, la forme du verre se déforme pendant l'animation. Le verre calcule ses limites à partir du cadre original de la vue, pas de sa présentation actuelle. Résultat : une capsule qui se transforme en cercle pendant la rotation, puis revient en capsule.

Solution : Pour les rotations, envisagez un pont vers UIKit avec UIVisualEffectView et UIGlassEffect via UIViewRepresentable. La configuration du coin de la vue UIKit sous-jacente évite ce bug de calcul.

Piège n°2 : les menus et le morphing

Ajouter un effet de verre aux composants Menu peut provoquer des animations qui glitchent lors du morphing. Le comportement varie selon la version d'iOS 26 :

// iOS 26.0 — appliquer glassEffect au Menu externe
Menu {
    Button("Option 1") { }
    Button("Option 2") { }
} label: {
    Image(systemName: "ellipsis")
        .frame(width: 44, height: 44)
}
.glassEffect(.regular.interactive())

// iOS 26.1+ — utiliser un ButtonStyle personnalisé
struct StyleBoutonVerre: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .glassEffect(.regular.interactive())
    }
}

Menu {
    Button("Option 1") { }
    Button("Option 2") { }
} label: {
    Image(systemName: "ellipsis")
        .frame(width: 44, height: 44)
}
.buttonStyle(StyleBoutonVerre())

Attention : sur iOS 26.1, ne placez pas de composant Menu à l'intérieur d'un GlassEffectContainer — ça casse les animations de morphing. C'est un problème connu qui devrait être corrigé dans une future mise à jour.

Piège n°3 : le verre ne peut pas échantillonner du verre

Règle fondamentale du système de rendu : un effet de verre ne peut pas échantillonner un autre effet de verre. Empiler des couches de Liquid Glass ne fonctionne pas et produit des artefacts visuels. C'est d'ailleurs l'une des raisons pour lesquelles le GlassEffectContainer existe — il fournit une région d'échantillonnage partagée qui résout ce problème.

Piège n°4 : les icônes SF Symbols

Si votre ancien design utilisait des variantes circulaires des SF Symbols (genre pencil.circle.fill), sachez que dans un contexte Liquid Glass, il vaut mieux utiliser les variantes de base (pencil) sans cercle. Le verre fournit déjà la forme conteneur — ajouter un cercle sur l'icône crée un double encadrement pas très élégant.

Migration d'une application existante vers le Liquid Glass

Si vous maintenez une app existante, bonne nouvelle : la migration vers le Liquid Glass est en grande partie automatique.

Ce qui change automatiquement

Quand vous compilez votre application pour iOS 26, les composants SwiftUI standard adoptent automatiquement le Liquid Glass :

  • TabView : la barre d'onglets devient une barre Liquid Glass
  • NavigationStack : la barre de navigation adopte le matériau de verre
  • Toolbar : les éléments de barre d'outils obtiennent le style Liquid Glass
  • Sheets et popovers : les présentations modales utilisent le nouveau matériau

Pour beaucoup d'apps, ça suffit pour obtenir un look modernisé sans toucher une seule ligne de code.

Ce qui nécessite votre intervention

Les interfaces personnalisées — boutons flottants maison, barres d'outils non standard, overlays sur mesure — ne se transformeront pas toutes seules. C'est là que les API glassEffect() et GlassEffectContainer entrent en jeu.

Voici un exemple de migration typique pour un bouton d'action flottant :

// AVANT — iOS 25 et antérieur
struct BoutonFlottantAvant: View {
    var body: some View {
        Button {
            // Action
        } label: {
            Image(systemName: "plus")
                .font(.title2)
                .foregroundStyle(.white)
                .frame(width: 56, height: 56)
                .background(Color.blue)
                .clipShape(Circle())
                .shadow(radius: 8)
        }
    }
}

// APRÈS — iOS 26 avec Liquid Glass
struct BoutonFlottantApres: View {
    var body: some View {
        Button {
            // Action
        } label: {
            Image(systemName: "plus")
                .font(.title2)
                .frame(width: 56, height: 56)
        }
        .glassEffect(.regular.tint(.blue.opacity(0.6)).interactive())
    }
}

Remarquez la simplification : plus besoin de gérer la couleur d'arrière-plan, le clip shape, ou l'ombre. Le Liquid Glass s'occupe de tout. On retire aussi le foregroundStyle(.white) — le système ajuste automatiquement la couleur du contenu pour assurer la lisibilité sur le verre.

Gestion de la compatibilité avec les versions antérieures

Si votre app doit supporter des versions iOS antérieures à iOS 26, pas de panique — utilisez la vérification de disponibilité :

struct BoutonAdaptatif: View {
    var body: some View {
        Button {
            // Action
        } label: {
            Image(systemName: "plus")
                .font(.title2)
                .frame(width: 56, height: 56)
        }
        .modifier(EffetVerreAdaptatif())
    }
}

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

Cette approche donne le Liquid Glass sur iOS 26+ et un fallback propre avec les matériaux existants sur les versions antérieures. Simple et efficace.

Performance : optimiser vos interfaces Liquid Glass

Le Liquid Glass, aussi beau soit-il, consomme des ressources GPU. Voici les bonnes pratiques pour garder des performances optimales.

Regrouper systématiquement dans des conteneurs

Comme on l'a vu, utilisez toujours un GlassEffectContainer quand vous avez plusieurs éléments de verre proches. La consolidation des couches de backdrop réduit significativement la charge GPU :

// Mauvais — chaque bouton a sa propre couche de backdrop
VStack {
    Button("A") { }.glassEffect()
    Button("B") { }.glassEffect()
    Button("C") { }.glassEffect()
}

// Bon — une seule couche de backdrop partagée
GlassEffectContainer {
    VStack {
        Button("A") { }.glassEffect()
        Button("B") { }.glassEffect()
        Button("C") { }.glassEffect()
    }
}

Limiter le nombre d'éléments de verre simultanés

Chaque effet de verre nécessite l'échantillonnage du contenu en arrière-plan. Plus vous avez d'éléments de verre visibles en même temps, plus le GPU travaille. En règle générale, essayez de ne pas dépasser 10 à 15 éléments de verre visibles simultanément sur un même écran.

Attention aux listes scrollables

Appliquer un effet de verre à chaque cellule d'une liste scrollable ? Très mauvaise idée. Non seulement c'est contraire à la philosophie du Liquid Glass (pas de verre sur le contenu, vous vous souvenez ?), mais les performances seront catastrophiques avec des dizaines de couches de backdrop créées et détruites pendant le défilement.

Profiler avec Instruments

Utilisez l'outil « GPU » dans Instruments de Xcode pour surveiller l'utilisation GPU de vos effets de verre. Surveillez particulièrement les pics de rendu hors écran (offscreen rendering) — c'est l'indicateur le plus direct de la charge imposée par le Liquid Glass.

Exemple complet : un lecteur de musique en Liquid Glass

Pour finir en beauté, voici un exemple plus complet qui rassemble toutes les techniques abordées — un mini-lecteur de musique avec une interface intégralement en Liquid Glass :

struct LecteurMusique: View {
    @State private var enLecture = false
    @State private var afficherDetails = false
    @Namespace private var espaceNom

    var body: some View {
        ZStack {
            // Pochette d'album en arrière-plan
            Image("pochette-album")
                .resizable()
                .scaledToFill()
                .ignoresSafeArea()

            VStack {
                Spacer()

                // Contrôles du lecteur
                GlassEffectContainer(spacing: 25) {
                    VStack(spacing: 16) {
                        if afficherDetails {
                            // Informations sur le morceau
                            VStack(spacing: 4) {
                                Text("Titre du morceau")
                                    .font(.headline)
                                Text("Nom de l'artiste")
                                    .font(.subheadline)
                                    .opacity(0.7)
                            }
                            .padding(.horizontal, 24)
                            .padding(.vertical, 12)
                            .glassEffect(.regular)
                            .glassEffectID("infos", in: espaceNom)

                            // Barre de progression
                            ProgressView(value: 0.4)
                                .padding(.horizontal, 20)
                                .glassEffectID("progression", in: espaceNom)
                        }

                        // Contrôles de lecture
                        HStack(spacing: 20) {
                            Button {
                                // Morceau précédent
                            } label: {
                                Image(systemName: "backward.fill")
                                    .font(.title3)
                                    .frame(width: 44, height: 44)
                            }
                            .contentShape(Circle())
                            .glassEffect(.regular.interactive())
                            .glassEffectID("precedent", in: espaceNom)

                            Button {
                                withAnimation(.bouncy(duration: 0.3)) {
                                    enLecture.toggle()
                                }
                            } label: {
                                Image(systemName: enLecture
                                    ? "pause.fill"
                                    : "play.fill")
                                    .font(.title)
                                    .frame(width: 56, height: 56)
                            }
                            .contentShape(Circle())
                            .glassEffect(
                                .regular
                                .tint(.blue.opacity(0.5))
                                .interactive()
                            )
                            .glassEffectID("lecture", in: espaceNom)

                            Button {
                                // Morceau suivant
                            } label: {
                                Image(systemName: "forward.fill")
                                    .font(.title3)
                                    .frame(width: 44, height: 44)
                            }
                            .contentShape(Circle())
                            .glassEffect(.regular.interactive())
                            .glassEffectID("suivant", in: espaceNom)
                        }

                        // Bouton pour afficher/masquer les détails
                        Button {
                            withAnimation(.bouncy(duration: 0.4)) {
                                afficherDetails.toggle()
                            }
                        } label: {
                            Image(systemName: afficherDetails
                                ? "chevron.down"
                                : "chevron.up")
                                .frame(width: 36, height: 20)
                        }
                        .glassEffect(.clear.interactive())
                        .glassEffectID("details", in: espaceNom)
                    }
                }
                .padding(.bottom, 40)
            }
        }
    }
}

Ce lecteur illustre l'utilisation combinée de :

  • GlassEffectContainer pour regrouper tous les contrôles
  • glassEffectID avec @Namespace pour les transitions de morphing
  • .interactive() pour les retours tactiles
  • .tint() pour distinguer le bouton principal
  • contentShape() pour les zones interactives correctes
  • Les variantes .regular et .clear pour la hiérarchie visuelle

Résumé et bonnes pratiques

Le Liquid Glass représente une évolution majeure du design sur les plateformes Apple. Voici les bonnes pratiques à garder en tête :

  1. Utilisez le Liquid Glass uniquement pour la navigation — barres d'outils, onglets, boutons flottants. Jamais pour le contenu principal.
  2. Regroupez avec GlassEffectContainer — toujours, quand vous avez plusieurs éléments de verre proches. C'est bon pour le look et pour la performance.
  3. Exploitez le morphing avec glassEffectID — les transitions fluides sont la signature du Liquid Glass. Utilisez-les pour les éléments qui apparaissent et disparaissent.
  4. Respectez l'accessibilité — vérifiez accessibilityReduceTransparency et basculez sur .identity quand nécessaire.
  5. Corrigez les zones interactives — ajoutez contentShape() pour que la zone tactile corresponde à la forme visuelle.
  6. Surveillez les performances — limitez le nombre d'éléments de verre simultanés et profilez avec Instruments.
  7. Préférez les SF Symbols sans variante circulaire — le verre fournit déjà la forme.
  8. Attention aux pièges connus — rotation, menus, et empilement de verre sur du verre.

Le Liquid Glass, c'est bien plus qu'un effet visuel — c'est un langage de design complet qui redéfinit la manière dont les utilisateurs interagissent avec les interfaces Apple. En maîtrisant ces API, vous pourrez créer des apps qui non seulement suivent les tendances de design Apple, mais offrent aussi une expérience utilisateur vraiment raffinée. L'ère du verre liquide ne fait que commencer, et franchement, c'est plutôt excitant.