How to access safe area size in SwiftUI?
IosSwiftSwiftuiIos Problem Overview
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.
Ios Solutions
Solution 1 - Ios
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
.
Solution 2 - Ios
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)
}
}
Solution 3 - Ios
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
Solution 4 - Ios
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)
}