How to use SafeAreaInsets in SwiftUI | SwiftUI Bootcamp #65
When you need a sticky floating bar — like a "Now Playing" strip at the bottom or a filter row at the top — that pushes your scrollable content out of the way instead of covering it, .safeAreaInset is the right tool. It lets you add UI to the edges of a scroll view while keeping all your content fully reachable.
What You'll Learn
- How
.safeAreaInset(edge:alignment:spacing:)adds persistent UI at the edges of a view - Why
.safeAreaInsetis better than.overlayfor persistent bottom or top bars - How the inset interacts with
ListandScrollViewto keep content accessible
Mental Model
Think of safe area insets like bumpers on a bowling lane. The safe area marks the region where content can safely sit without being obscured by system UI (notch, home indicator, navigation bar). When you add a .safeAreaInset(edge: .bottom), you're extending those bumpers inward — you're telling SwiftUI "treat my custom view as if it were part of the system UI, and keep all scrollable content above it."
The contrast with .overlay is important. An overlay floats on top of the content — content can scroll under it and become hidden. A safe area inset actually pushes the scroll view's content area so the last item always scrolls just above your bar, never behind it.
Detailed Explanation
.safeAreaInset(edge:alignment:spacing:content:) was introduced in iOS 15 as a cleaner alternative to overlaying persistent UI elements. You attach it to a scroll container (like List or ScrollView) and provide a view. SwiftUI adds that view to the specified edge and adjusts the scrollable content area so no content is hidden behind it.
The edge parameter (.top, .bottom, .leading, .trailing) determines which edge the inset appears on. alignment controls how the inset view is aligned along the perpendicular axis — for example, .trailing on a top inset positions the content to the right. spacing adds a gap between the inset view and the scroll view's content; passing nil uses the default.
The inset view itself is not scrollable — it remains fixed in place while the list or scroll view scrolls beneath it. This is ideal for "Now Playing" bars, floating action buttons that should always be visible, filter chips, or a sticky promotional banner.
Note that edgesIgnoringSafeArea(.bottom) on the background of the inset view extends the background color behind the home indicator, which gives a polished "full bleed" look that matches native Apple apps.
Code Structure
SafeAreaInsetBootcamp.swift shows a List of rectangles inside a NavigationStack with two safe area insets — one at the top and one at the bottom. This makes it immediately clear how both edges behave. The commented-out .overlay is preserved so you can compare the behavior: comment it back in to see why overlay doesn't push content.
Complete Code
SafeAreaInsetBootcamp.swift
import SwiftUI
struct SafeAreaInsetBootcamp: View {
var body: some View {
NavigationStack {
List(0..<10) { _ in
Rectangle()
.frame(height: 300)
}
.navigationTitle("Safe Area Insets")
.navigationBarTitleDisplayMode(.inline)
// .overlay(
// Text("Hi")
// .frame(maxWidth: .infinity)
// .background(Color.yellow)
//
// ,alignment: .bottom
// )
// Adds a sticky bar at the top — list content scrolls below it, never behind it
.safeAreaInset(edge: .top, alignment: .trailing, spacing: nil) {
Text("Hi")
.frame(maxWidth: .infinity)
// .padding()
.background(Color.yellow.edgesIgnoringSafeArea(.bottom)) // Extends bg behind safe area edge
// .clipShape(Circle())
// .padding()
}
// Adds a sticky bar at the bottom — last list item always scrolls above it
.safeAreaInset(edge: .bottom, alignment: .trailing, spacing: nil) {
Text("Hi")
.frame(maxWidth: .infinity)
// .padding()
.background(Color.yellow.edgesIgnoringSafeArea(.bottom)) // Prevents gap above home indicator
// .clipShape(Circle())
// .padding()
}
}
}
}
struct SafeAreaInsetBootcamp_Previews: PreviewProvider {
static var previews: some View {
SafeAreaInsetBootcamp()
}
}Code Walkthrough
List(0..<10) { _ in Rectangle().frame(height: 300) }— A list of tall rectangles creates enough content to scroll. This is intentional — without scrollable content, the difference between.safeAreaInsetand.overlaywouldn't be apparent..safeAreaInset(edge: .top, alignment: .trailing, spacing: nil)— This places a bar at the top of the list. Thealignment: .trailingmeans the inset view itself is right-aligned. The list content area starts below this bar — you can scroll all the way to the top and the first item will be fully visible below the inset.Text("Hi").frame(maxWidth: .infinity)— Using.frame(maxWidth: .infinity)stretches the text view to fill the full width, creating a proper bar. Without this, the text would be only as wide as its content..background(Color.yellow.edgesIgnoringSafeArea(.bottom))— TheedgesIgnoringSafeArea(.bottom)on the background means the yellow color extends all the way to the bottom edge of the screen (behind the home indicator), eliminating any white gap. This is a common polishing detail in production apps..safeAreaInset(edge: .bottom, ...)— The bottom inset is what prevents the last list item from being obscured. Scroll to the end — the last rectangle is fully visible above the yellow bar. Compare this with the commented-out.overlaywhich would cover the bottom of the last item.
Common Mistakes
Mistake: Using .overlay for persistent floating bars instead of .safeAreaInset
With .overlay, the last items in a list can scroll behind the bar and become inaccessible (especially on lists without rubber-band scrolling). .safeAreaInset adjusts the content inset so the last item always scrolls to a fully visible position.
Mistake: Applying .safeAreaInset outside a scroll container.safeAreaInset is most useful on scrollable containers (List, ScrollView). Applied to a static VStack it technically works, but its main benefit — keeping scrolled content visible — doesn't apply. Use padding or spacers in static layouts instead.
Mistake: Forgetting that the inset view is always visible, even when content is short
The inset bar shows regardless of whether the content needs scrolling. If you have a short list with only one item, the bottom bar will still appear. Consider hiding the bar conditionally if the content is too short to need it.
Key Takeaways
.safeAreaInsetshrinks the scrollable content area so inset content is never hidden behind your custom bar — unlike.overlaywhich floats on top- Apply it directly to
ListorScrollViewto get the content inset behavior - Use
edgesIgnoringSafeAreaon the inset view's background to extend it behind the home indicator for a native appearance
Last updated: June 27, 2026