- Published on
> Understanding Safe Areas in SwiftUI
- Authors

- Name
- Mick MacCallum
- @0x7fs
Safe areas define the region of the screen where your content won't be obscured by system UI—the notch, Dynamic Island, home indicator, status bar, or navigation bars. SwiftUI respects safe areas by default, which is usually what you want. But sometimes you need a background image to extend edge-to-edge or a custom tab bar to sit at the bottom. That's where understanding safe area behavior becomes important.
Default Behavior
By default, SwiftUI keeps your content within safe areas. If you create a simple view:
struct ContentView: View {
var body: some View {
Color.blue
}
}
The blue color fills the safe area but doesn't extend under the notch or home indicator. This is intentional—text and interactive elements shouldn't be hidden under system UI.
When you embed views in a NavigationStack or TabView, those containers manage their own safe areas. Content automatically avoids the navigation bar and tab bar without any extra work on your part.
Ignoring Safe Areas
To extend content beyond safe areas, use the ignoresSafeArea() modifier:
struct ContentView: View {
var body: some View {
Color.blue
.ignoresSafeArea()
}
}
Now the blue extends to the true edges of the screen, including under the notch and home indicator. This is common for background colors and images where you want a seamless edge-to-edge appearance.
You can be selective about which edges to ignore:
Image("hero")
.resizable()
.ignoresSafeArea(edges: .top) // Extends under status bar only
And you can specify which types of safe areas to ignore. The two main types are .container (navigation bars, tab bars) and .keyboard:
TextField("Message", text: $message)
.ignoresSafeArea(.keyboard) // Don't shift when keyboard appears
Layering Content Correctly
A common pattern is having a background that extends edge-to-edge while keeping text and controls within safe areas:
struct ContentView: View {
var body: some View {
ZStack {
Image("background")
.resizable()
.ignoresSafeArea()
VStack {
Text("Welcome")
.font(.largeTitle)
Spacer()
Button("Get Started") { }
}
.padding()
}
}
}
The image ignores safe areas and fills the screen. The VStack respects safe areas, so the text stays visible and the button remains tappable above the home indicator.
Reading Safe Area Insets
When you need to know the exact safe area dimensions—perhaps for custom positioning—use GeometryReader:
GeometryReader { geometry in
VStack {
Text("Top inset: \(geometry.safeAreaInsets.top)")
Text("Bottom inset: \(geometry.safeAreaInsets.bottom)")
}
}
This is useful when building custom layouts that need to account for safe areas manually, such as a custom bottom sheet or floating action button.
Safe Areas and Keyboards
The keyboard creates its own safe area. By default, SwiftUI shifts content up to keep focused text fields visible. Usually this is helpful, but sometimes it causes unwanted layout shifts.
If you have a view that shouldn't move when the keyboard appears:
VStack {
// Content that shouldn't shift
}
.ignoresSafeArea(.keyboard)
For input fields in sheets or forms, you typically want the default behavior so users can see what they're typing.
Safe Areas in Scroll Views
ScrollView handles safe areas intelligently. Content scrolls under navigation bars with appropriate insets, and the scroll indicators stay within bounds. If you're building a custom scroll experience and need to adjust this behavior:
ScrollView {
LazyVStack {
ForEach(items) { item in
ItemRow(item: item)
}
}
}
.contentMargins(.horizontal, 20, for: .scrollContent)
.scrollIndicatorsFlash(onAppear: true)
The contentMargins modifier (iOS 17+) gives you fine-grained control over scroll content positioning relative to safe areas.
Common Patterns
For a full-screen image with overlaid controls:
struct FullScreenImage: View {
var body: some View {
ZStack(alignment: .bottomTrailing) {
AsyncImage(url: imageURL) { image in
image
.resizable()
.scaledToFill()
} placeholder: {
Color.gray
}
.ignoresSafeArea()
Button {
// Share action
} label: {
Image(systemName: "square.and.arrow.up")
.padding()
.background(.ultraThinMaterial)
.clipShape(Circle())
}
.padding()
}
}
}
For a custom header that extends under the status bar:
struct CustomHeader: View {
var body: some View {
VStack(spacing: 0) {
LinearGradient(colors: [.blue, .purple], startPoint: .leading, endPoint: .trailing)
.frame(height: 120)
.ignoresSafeArea(edges: .top)
.overlay(alignment: .bottom) {
Text("Profile")
.font(.title)
.foregroundStyle(.white)
.padding(.bottom, 16)
}
// Rest of content
List { /* ... */ }
}
}
}
Debugging Safe Areas
If your layout isn't respecting safe areas as expected, add a debug border:
content
.border(.red)
.ignoresSafeArea()
.border(.blue)
The red border shows the view's frame before ignoring safe areas; the blue shows it after. This visual debugging helps identify where safe area adjustments are being applied.
When to Ignore Safe Areas
As a general rule, ignore safe areas for decorative elements (backgrounds, images, gradients) but respect them for interactive content (buttons, text fields, lists). Users expect to tap without reaching under the notch, and they expect to read without text being cut off by the home indicator.
The exceptions are intentional full-screen experiences—media players, cameras, games—where you want complete control over the display. Even then, consider keeping critical UI like close buttons within safe margins.
// Continue_Learning
How to Resize an Image in SwiftUI
Learn how to resize images in SwiftUI using resizable(), frame(), and different content modes like scaledToFit and scaledToFill.
How to Round Specific Corners of a View in SwiftUI
Learn how to round only certain corners of a SwiftUI view using UnevenRoundedRectangle, custom shapes, and clipShape.
How to Change the Background Color of a View in SwiftUI
Learn different ways to set background colors on SwiftUI views, from simple color fills to gradients and materials.
// Stay Updated
Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.