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ètrealignmentpour 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 😉.
safeAreaBarrespectescrollEdgeEffectStyle. Si tu personnalises l'effet de bord du scroll dans ta vue (par exemple.scrollEdgeEffectStyle(.soft, for: .bottom)), la barre s'adapte au style.safeAreaInsetignore 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!


Commentaires
Aucun commentaire pour le moment. Sois le premier !