BS
BleepingSwift
Published on

> Understanding Safe Areas in SwiftUI

Authors
  • avatar
    Name
    Mick MacCallum
    Twitter
    @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.

subscribe.sh

// Stay Updated

Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.

>

By subscribing, you agree to our Privacy Policy.