I am looking for a pure SwiftUI based solution to determine whether the iOS device has top notch based on the safe area insets of the root view. While this is easy to determine in UIKit, for SwiftUI the only solution that I have found so far is also UIKittish, that is:
extension UIApplication {
var currentWindow: UIWindow? {
connectedScenes
.compactMap {
$0 as? UIWindowScene
}
.flatMap {
$0.windows
}
.first {
$0.isKeyWindow
}
}
}
private struct SafeAreaInsetsKey: EnvironmentKey {
static var defaultValue: EdgeInsets {
UIApplication.shared.currentWindow?.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)
}
}
While it's likely that SwiftUI will continue to use UIKit elements such as UIWindow
under the hood so the solution above will continue to work for next several years, I still want to know if there is a pure SwiftUI based solution to this.
You can get safe area insets from a GeometryReader
. You can put a GeometryReader
at the very top of the view hierarchy, perhaps directly in WindowGroup { ... }
. Then you can send the insets down with an environment key.
WindowGroup {
GeometryReader { geo in
// putting the text directly in a GeometryReader makes it align to the top left
// which is why I am nesting it in a ZStack that covers the whole screen here
// any other view that fills all the available space works too
ZStack {
ContentView() // the rest of the app is here
.environment(\.safeAreaInsets, geo.safeAreaInsets)
}.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct ContentView: View {
@Environment(\.safeAreaInsets) var insets
var body: some View {
Text("\(insets.top)")
}
}
private struct SafeAreaInsetsKey: EnvironmentKey {
static var defaultValue: EdgeInsets = .init()
}
extension EnvironmentValues {
var safeAreaInsets: EdgeInsets {
get { self[SafeAreaInsetsKey.self] }
set { self[SafeAreaInsetsKey.self] = newValue }
}
}
Note that you need to preview the whole GeometryReader
for this to work in Xcode Previews. You can encapsulate the geometry reader and ZStack
into a view modifier like this, so that you can conveniently use it in Previews:
extension View {
func readSafeAreaInsets() -> some View {
GeometryReader { geo in
ZStack {
self.environment(\.safeAreaInsets, geo.safeAreaInsets)
}.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
#Preview {
ContentView().readSafeAreaInsets()
}