Tous les articles
6 min·

safeAreaInset vs safeAreaBar en SwiftUI

safeAreaInset et safeAreaBar épinglent du contenu fixe en bord d'écran. Différences visuelles, pièges et guide pour choisir le bon modifier SwiftUI.

Par Carolane Lefebvre
safeAreaInset vs safeAreaBar en SwiftUI

Tu veux coller un bouton ou une barre en bas de ton écran, par-dessus une scroll view, sans que le contenu passe en dessous ? C'est exactement le job de ces deux modifiers. Mais depuis iOS 26, Apple en a ajouté un deuxième, et la différence n'est pas juste cosmétique 👀.

Dans cet article, on va voir ce que chacun fait, comment ils se comportent visuellement, quels pièges éviter, et surtout lequel utiliser selon ton contexte.


Le problème de base

Imagine un écran classique avec une liste qui scroll, un bouton “Continuer” fixé en bas. Ce pattern n’est pas unique, tu le retrouves partout : page de paiement, onboarding, formulaire multi-étapes, player audio, etc…

Avant iOS 15, la solution c’était un bon ZStack bricolé. Tu empilais ta scroll view et ton bouton, puis tu ajoutais un .padding(.bottom, [value]) sur le contenu pour qu’il ne soit pas masqué par le bouton.

Et là tu vois peut-être le problème : comment on peut ajuster la valeur de ce padding ? Il faudrait deviner la hauteur de la barre, gérer le home indicator sur les iPhones sans bouton physique, et adapter ce padding quand le contenu de la barre changeait : des textes plus long, des boutons plus grands.

Tu vois le résultat : un cauchemar de maintenance… 🥲

Depuis iOS 15, safeAreaInset a réglé ça proprement, le système calcule l’espace nécessaire automatiquement. Depuis iOS 26, safeAreaBar propose une alternative avec un rendu visuel different, plus intégré au design system d’Apple. Regardons ce que ça donne.

.safeAreaInset : le classique qui fait le job

C’est le modifier qu’on utilise depuis des années (vu qu’il est apparu sous iOS 15). Tu lui donnes un bord : .bottom, .top, .leading, .trailing, et une vue dans un view builder. Il épingle ta vue à ce bord, et le contenu scrollable se décale automatiquement pour lui laisser de la place. Plus besoin de calculer quoi que ce soit, le système mesure ta barre et ajuste l’inset tout seul !

ScrollView {
    LazyVStack {
        ForEach(steps) { step in
            StepRow(step)
        }
    }
}
.safeAreaInset(edge: .bottom) {
    Button("Continue") { /* ... */ }
        .buttonStyle(.borderedProminent)
        .padding()
}

Concrètement le bouton se pose en bas avec son propre background opaque. Le contenu scroll et s’arrête juste au-dessus. C’est net, c’est solide, et c’est surtout prévisible !

💡Ce qu’il faut s’avoir :

  • Ça fonctionne avec n’importe contenu scrollabe (ScrollView, List, Form, TextEditor).

  • L’alignement par défaut est .center, mais tu peux le center avec le paramètre alignment pour coller ta barre à gauche (.leading) ou à droite (.trailing).

Exemple : un player audio

ScrollView {
    LazyVStack {
        ForEach(songs) { song in
            SongRow(song)
        }
    }
}
.safeAreaInset(edge: .bottom) {
    MiniPlayerView(currentSong: nowPlaying)
        .background(.regularMaterial)
}

.safeAreaBar : le nouveau d’iOS26

On retrouve la même structure que .safeAreaInset : un bord, un view builder. Alors c’est quoi le twist ?

Ici, la différence se joue entièrement sur le rendu visuel, et c’est là que ça devient intéressant.

ScrollView {
    LazyVStack {
        ForEach(steps) { step in
            StepRow(step)
        }
    }
}
.safeAreaBar(edge: .bottom) {
    Button("Continuer") { /* ... */ }
        .buttonStyle(.borderedProminent)
        .padding()
}

Avec safeAreaBar, la barre devient translucide. Le contenu qui scroll en-dessous ne disparaît pas brusquement, il s’estompe progressivement dans la barre, exactement comme le font les toolbars et tab bars système depuis iOS 26. C’est le même effet de fondu que l’on peut voir dans des applications comme Messages, Safari ou Mail quand tu scroll vers le bas.

Forcément, safeAreaBar étant sorti avec iOS 26, il est compatible avec le Liquid Glass. Si ton app adopte le nouveau design d’iOS 26, tes barres custom se comportent comme les barres natives : translucides, fluides, intégrées au chrome system. Visuellement, l’utilisateur ne fait plus a différence entre ta barre native et une custom intégrée avec UIKit/SwiftUI.

💡Quelques subtilités:

  • Le contenu est visible sous la barre. C’est voulu, c’est tout l’intérêt de la translucidité. Mais ça veut dire que si tu mets un texte important dans le dernier élément de ta liste, il sera partiellement visible sous la barre. Pense à tester 😉.

  • safeAreaBar respecte scrollEdgeEffectStyle. Si tu personnalises l'effet de bord du scroll dans ta vue (par exemple .scrollEdgeEffectStyle(.soft, for: .bottom)), la barre s'adapte au style. safeAreaInset ignore ce modifier complètement.

  • Ça fonctionne aussi en .top . Un header translucide qui s’intègre au troll, c’est un use case tout à fait valide (regarde Mail sous iOS 26).


Lequel choisir ?

Tu dois supporter iOS 18 ou plus ancien ?

Utilise safeAreaInset, puisque safeAreaBar n’existe qu’à partir d’iOS 26. Si tu l’appelles sans ajouter le flag #available, ton app crachera sur les versions précédentes.

Ta barre doit être totalement opaque et séparer du contenu ?

safeAreaInset, même si tu cibles iOS 26. C'est le bon choix pour un bouton d'action avec un fond coloré, une barre de prix dans un panier, un mini player, ou n'importe quel élément qui doit se détacher visuellement du reste. La translucidité de safeAreaBar serait contre-productive ici, tu veux que l'utilisateur voie clairement la barre comme un élément distinct.

Tu es sur iOS 26 et ta barre doit s’intégrer au look système

safeAreaBar. C'est le choix naturel si tu adoptes Liquid Glass et que tu veux que tes barres custom se fondent dans le chrome de l'OS. C'est aussi le bon choix si ta barre contient des contrôles de navigation secondaires qui ressemblent à des toolbars, l'effet de fondu les rend plus naturels.

Tu veux supporter les deux ?

Tu peux jouer avec le ViewModifier avec un availability check qui te permet de basculer automatiquement :

struct AdaptiveBottomBar<Bar: View>: ViewModifier {
    @ViewBuilder var bar: Bar

    func body(content: Content) -> some View {
        if #available(iOS 26, *) {
            content.safeAreaBar(edge: .bottom) { bar }
        } else {
            content.safeAreaInset(edge: .bottom) { bar }
        }
    }
}

extension View {
    func adaptiveBottomBar<Bar: View>(@ViewBuilder bar: () -> Bar) -> some View {
        modifier(AdaptiveBottomBar(bar: bar()))
    }
}

Que tu peux ensuite utiliser dans tes views :

ScrollView {
    // ton contenu
}
.adaptiveBottomBar {
    Button("Continuer") { /* ... */ }
        .buttonStyle(.borderedProminent)
        .padding()
}

safeAreaBar sur iOS 26+, safeAreaInset en fallback. Même API dans ton code, le bon rendu sur chaque version, zéro #available à gérer dans tes écrans.

À retenir

safeAreaInset n'est pas obsolète et ne va nulle part, c'est toujours le bon choix pour la rétrocompatibilité et pour les barres qui doivent rester opaques et visuellement séparées du contenu. C'est le modifier par défaut tant que tu supportes iOS 25 ou plus ancien.

safeAreaBar est ce qu'iOS 26 attend : translucide, intégré au système, compatible Liquid Glass. Si ton deployment target est iOS 26 et que ta barre doit faire partie du chrome de l'app plutôt que flotter au-dessus du contenu, c'est lui.

Même forme d'API, mais une philosophie visuelle complètement différente. Ne choisis pas par défaut, choisis en fonction de ce que tu veux que l'utilisateur perçoive.

En espérant que ça puisse t’être utile, have fun coding!

App mentionnée

Keepio

Capture et organise tes idées

Commentaires

Connecte-toi pour laisser un commentaire.

Aucun commentaire pour le moment. Sois le premier !

Voir d'autres articles

.task vs .onAppear vs .refreshable en SwiftUI

iOS 26 a livré 5 améliorations discrètes en SwiftUI

5 modifiers SwiftUI pour maitriser vos Sheets