I want to manually set the frame height of a view in SwiftUI to the size of the safe area of the screen. It's easy to get the bounds of the screen (UIScreen.main.bounds
), but I can't find a way to access the size of the safe area.

- 67,741
- 15
- 184
- 220

- 1,046
- 1
- 9
- 11
6 Answers
You can use a GeometryReader
to access the safe area.
See: https://developer.apple.com/documentation/swiftui/geometryreader.
struct ContentView : View {
var body: some View {
GeometryReader { geometry in
VStack {
Spacer()
Color.red
.frame(
width: geometry.size.width,
height: geometry.safeAreaInsets.top,
alignment: .center
)
.aspectRatio(contentMode: ContentMode.fit)
}
}
.edgesIgnoringSafeArea(.bottom)
}
}
But FYI: The safe area is not a size. It is an EdgeInsets
.

- 4,383
- 1
- 34
- 42

- 2,802
- 1
- 18
- 19
-
32This doesn't work if you use .ignoresSafeArea on a parent view. – FarouK May 01 '21 at 18:44
If you use edgesIgnoringSafeArea
on an parentView and you want to access the device UISafeAreaInsets
you can do the following:
Code
private struct SafeAreaInsetsKey: EnvironmentKey {
static var defaultValue: EdgeInsets {
(UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.safeAreaInsets ?? .zero).insets
}
}
extension EnvironmentValues {
var safeAreaInsets: EdgeInsets {
self[SafeAreaInsetsKey.self]
}
}
private extension UIEdgeInsets {
var insets: EdgeInsets {
EdgeInsets(top: top, leading: left, bottom: bottom, trailing: right)
}
}
Usage
struct MyView: View {
@Environment(\.safeAreaInsets) private var safeAreaInsets
var body: some View {
Text("Ciao")
.padding(safeAreaInsets)
}
}

- 3,251
- 2
- 17
- 35
-
11It's not safe to assume windows[0] will return a UIWindow. As per info from this blog by Padam Thapan (https://padamthapa.com/blog/how-to-get-deprecated-keywindow-in-swift/), the first element in this array may become a UITextEffectsWindow when keypad is present on-screen. I feel like the better solution would be to wrap a reference to keyWindow in a struct and put it into Environment. This will allow us to access the above extension just as conveniently. – Shengchalover Apr 29 '21 at 10:56
-
@Shengchalover Thanks for the feedback, I updated my answer. I decided to don't catch the reference to keyWindow because we don't know if it will change. – Lorenzo Fiamingo Apr 29 '21 at 16:04
-
This code doesn't compile, you should use: static var defaultValue: EdgeInsets { UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.safeAreaInsets.insets ?? EdgeInsets() } – FarouK May 01 '21 at 18:49
-
1
-
Using this to center the View in landscape because of the home indicator, the offset of "y" should only be applied if the orientation is in landscape, shouldn't it? – sheldor Jun 03 '21 at 13:31
UIApplication.shared.windows is deprecated, you can now use connectedScenes:
import SwiftUI
extension UIApplication {
var keyWindow: UIWindow? {
connectedScenes
.compactMap {
$0 as? UIWindowScene
}
.flatMap {
$0.windows
}
.first {
$0.isKeyWindow
}
}
}
private struct SafeAreaInsetsKey: EnvironmentKey {
static var defaultValue: EdgeInsets {
UIApplication.shared.keyWindow?.safeAreaInsets.swiftUiInsets ?? EdgeInsets()
}
}
extension EnvironmentValues {
var safeAreaInsets: EdgeInsets {
self[SafeAreaInsetsKey.self]
}
}
private extension UIEdgeInsets {
var swiftUiInsets: EdgeInsets {
EdgeInsets(top: top, leading: left, bottom: bottom, trailing: right)
}
}
And then use Environment property in your View to get safe area insets:
@Environment(\.safeAreaInsets) private var safeAreaInsets

- 2,231
- 2
- 21
- 17
-
1
-
I seem to get a few errors when using this: `'EdgeInsets' is ambiguous for type lookup in this context` and `Type 'SafeAreaInsetsKey' does not conform to protocol 'EnvironmentKey'`. Anyone know if there's an answer that works now? – MichaelGofron Apr 02 '23 at 10:48
Not sure why the accepted answer uses top inset for a view placed under the bottom one - these are not the same.
Also if you correct this "typo", you'll see that edgesIgnoringSafeArea
called on a GeometryReader
zeros the corresponding value. Looks like it wasn't the case back on iOS 13, but now it is, so you need to call edgesIgnoringSafeArea
on a GeometryReader
child instead, and this code still works as expected on iOS 13:
GeometryReader { geometry in
VStack {
Spacer()
Color.red
.frame(
width: geometry.size.width,
height: geometry.safeAreaInsets.bottom,
alignment: .center
)
.aspectRatio(contentMode: ContentMode.fit)
}
.edgesIgnoringSafeArea(.bottom)
}

- 67,741
- 15
- 184
- 220
-
I think you're missing a word in "... a `GeometryReader` zeros corresponding value"? In any case I worked it out; does SwiftUI have a "no-op" container for situations like this or do you just use a `VStack` even if you don't technically need one? – Robert Atkins Sep 16 '21 at 14:25
-
@RobertAtkins I'm not sure what word I might have missed here? I meant to say that if you disable `edgesIgnoringSafeArea` outside `GeometryReader`, you cannot get `geometry.safeAreaInsets` inside (the values will be zero). Using `geometry` we can get the dimensions we need, but to put the view in the right place I use `VStack`: `Spacer` takes all the remaining space and pushes my view down. – Phil Dukhov Sep 16 '21 at 14:34
-
“… zeroes _the_ corresponding value” maybe? And yeah, I know a spacer will push things out of whack but I find it weird to use a `VStack` with only one thing in it. That’s not really germane to the original question though. – Robert Atkins Sep 17 '21 at 19:23
-
@RobertAtkins now I get it, thanks, I'm not a native speaker, so I make mistakes like that. It depends on the problem you need to solve, but in general it is not a bad practice to use `VStack` in this way. Drawing a view like this will not be a high-load operation. You can set the alignment of a parent view, but if you want different alignments for different children, you can use this method. – Phil Dukhov Sep 18 '21 at 08:27
You can also create a custom EnvironmentValue
and pass the safe area insets over from an "initial View
". This works perfectly for me!
Creating EnvironmentValue
private struct SafeAreaInsetsEnvironmentKey: EnvironmentKey {
static let defaultValue: (top: CGFloat, bottom: CGFloat) = (0, 0)
}
extension EnvironmentValues {
var safeAreaInsets: (top: CGFloat, bottom: CGFloat) {
get { self[SafeAreaInsetsEnvironmentKey.self] }
set { self[SafeAreaInsetsEnvironmentKey.self] = newValue }
}
}
Setting
The idea is to do this before any potential View
parent uses .edgesIgnoringSafeArea
, this is required for it to work. For instance:
@main
struct YourApp: App {
@State private var safeAreaInsets: (top: CGFloat, bottom: CGFloat) = (0, 0)
var body: some Scene {
WindowGroup {
ZStack {
GeometryReader { proxy in
Color.clear.onAppear {
safeAreaInsets = (proxy.safeAreaInsets.top, proxy.safeAreaInsets.bottom)
}
}
ContentView()
.environment(\.safeAreaInsets, safeAreaInsets)
}
}
}
}
Usage
struct SomeChildView: View {
@Environment(\.safeAreaInsets) var safeAreaInsets
...
}

- 6,640
- 7
- 43
- 66
extension UIScreen {
static var topSafeArea: CGFloat {
let keyWindow = UIApplication.shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow}).first
return (keyWindow?.safeAreaInsets.top) ?? 0
}
}
Usage:
UIScreen.topSafeArea

- 6,115
- 4
- 35
- 36