The Safe Area
The safe area is the part of the screen that is visible to the user without being obstructed by system-provided controls such as the status bar, navigation bar, home indicator or tab bars. In the example below, the safe area is marked in green.
In the example above, we can see that the scroll view becomes exactly as large as the safe area, yet its content is still visible outside of the safe area. Put another way, we could say that the scroll view in the example above is exactly as large as the screen, but has its content inset to the size of the safe area. To the outside, however, the bounds of the scroll view are the bounds of the safe area. This way, when the user scrolls all the way to the top or bottom, the content is not obstructed.
When we select the second tab, we can see that the resizable image fills the safe area exactly, but does not extend beyond the safe area. Most views in SwiftUI behave like this, but we can also control this ourselves. In the third tab, we used a grid but made it ignore the safe area. It stretches behind the navigation bar and toolbar, all the way to the edges of the screen.
Ignoring the Safe Area
In the example below, we have added two fake toolbars to the top and bottom of the screen using the safeAreaInset
modifier. When we add the ignoresSafeArea
modifier to our color, the view stretches beyond the safe area.
Color.orange .padding(.horizontal) .ignoresSafeArea() .safeAreaInset(edge: .top) { MyToolbar() } .safeAreaInset(edge: .bottom) { MyToolbar() }
For more complicated hierarchies, it matters a lot where we apply the ignoresSafeArea
modifier. In the example below, we have a vertical stack with two background colors. When we apply the modifier to the VStack, the entire stack stretches beyond the safe area. When we apply the modifier to the individual views, only the individual views stretch beyond the safe area. To see the difference, try enabling only the top bar or only the bottom bar and then toggling the placement of the modifier. We'll see that the stack still distributes the space evenly, but the individual views can then stretch beyond the safe area.
Custom Insets
As we have seen above, we can add to the safe area using the safeAreaInset
modifier. (As of iOS 17, we can also use safeAreaPadding to extend the safe area without providing a view). The safe area insets work like a stack: when we have an existing inset, additional modifiers append their size to the inset. In the example below, we can add multiple bottom bars to the screen, and the bottom safe area inset is their combined height.
Color.orange .padding(.horizontal) .ignoresSafeArea() .safeAreaInset(edge: .bottom, spacing: 0) { MyToolbar() } .safeAreaInset(edge: .bottom, spacing: 0) { MyToolbar() }
Note that we might also want to use ignoresSafeArea
within safeAreaInset
views. For example, to show the bottom cart button behind the home indicator, we need to addignoresSafeArea
to exactly the right view. When we add it to the cart the text will be obscured, but if we add it to the background, we get the effect we want.
ProductPlaceholder() .safeAreaInset(edge: .top, spacing: 0) { MyToolbar() } .safeAreaInset(edge: .bottom, spacing: 0) { HStack(alignment: .firstTextBaseline) { ZStack { Circle() .frame(width: 18, height: 18) Text("1") .foregroundStyle(.accentColor) } Text("My Order") Spacer() Text("$19") } .foregroundStyle(.white) .padding(8) .frame(maxWidth: .infinity) .background { Color.accentColor .ignoresSafeArea() } /* .ignoresSafeArea() */ }
Gotchas
When you use the background
modifier, you might get the unexpected effect of a view stretching beyond the safe area. This happens because there are multiple variants of the background modifier. When we use the background modifier without a trailing closure and with no other parameters, the background(_:ignoresSafeAreaEdges:) variant is used, which takes a ShapeStyle
instead of a View
and has a default value of all
for the edges. The Swift compiler chooses this overload because the type we provide (Color
) is a more specific match than the other overloads. We can see the difference in the following example:
Text("Hello, world.") .font(.title) .foregroundStyle(.white) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.purple) /* .background { Color.purple } */
Geometry Reader
When you need programmatic access to the values of the safe area, you can use a geometry reader. In the example below, we visualized the size of the geometry reader using a blue background. When the geometry reader is not ignoring the safe area, the proxy's safeAreaInsets
property contains non-zero values. When you ignore the safe area, the geometry reader fills the entire screen but the insets will be zero.
GeometryReader { proxy in VStack { Text( "\(proxy.size.width) x \(proxy.size.height)" ) Text( "top: \(proxy.safeAreaInsets.top), bottom: \(proxy.safeAreaInsets.bottom)" ) } .frame(width: proxy.size.width, height: proxy.size.height) .font(.caption)}.foregroundStyle(.white).background { Color.accentColor }/* .ignoresSafeArea() */.safeAreaInset(edge: .top, spacing: 0) { MyToolbar()}.safeAreaInset(edge: .bottom, spacing: 0) { MyToolbar()}
You can also use a geometry reader to provide a visualization of the safe area in your own code.
More Resources
In this article, Fatbobman shows a number of both simple and advanced examples of how to work with the safe area in SwiftUI.
https://fatbobman.com/en/posts/safearea/In SwiftUI's official documentation we can see how to add a background to a view, and how to use the safe area insets to make sure the background is visible even when the keyboard shows.
https://developer.apple.com/documentation/swiftui/adding-a-background-to-your-viewThe Human Interface Guidelines contains an article about layout that talks about the safe area from a design perspective.
https://developer.apple.com/design/human-interface-guidelines/layout