Reputation: 9206
What would be the most proper way to get both top and bottom height for the unsafe areas?
Upvotes: 291
Views: 332818
Reputation: 5176
This solution worked for my project...
In Swift - iOS 15.0+
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
let topPadding = windowScene?.windows.first?.safeAreaInsets.top ?? 0
Upvotes: -1
Reputation: 147
Here is a very simple solution to get top and bottom safe area height for all devices
let topSpace = view.safeAreaInsets.top
let bottomSpace = view.saferAreaInsets.bottom
Upvotes: 1
Reputation: 1054
Avoiding the warning
'windows' was deprecated in iOS 15.0: Use UIWindowScene.windows on a relevant window scene instead
Tested in Xcode 15.1
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
let safeAreaInsets = window?.safeAreaInsets
Upvotes: 4
Reputation: 4109
For SwiftUI:
Code
private struct SafeAreaInsetsKey: EnvironmentKey {
static var defaultValue: EdgeInsets {
guard let scene = UIApplication.shared.connectedScenes.first,
let windowSceneDelegate = scene.delegate as? UIWindowSceneDelegate,
let window = windowSceneDelegate.window,
let safeAreaInsets = window?.safeAreaInsets else {
return .zero
}
return safeAreaInsets
}
}
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)
}
}
Upvotes: 3
Reputation: 7350
Try this :
In Objective C
if (@available(iOS 11.0, *)) {
UIWindow *window = UIApplication.sharedApplication.windows.firstObject;
CGFloat topPadding = window.safeAreaInsets.top;
CGFloat bottomPadding = window.safeAreaInsets.bottom;
}
In Swift
if #available(iOS 11.0, *) {
let window = UIApplication.shared.keyWindow
let topPadding = window?.safeAreaInsets.top
let bottomPadding = window?.safeAreaInsets.bottom
}
In Swift - iOS 13.0+
// Use the first element from windows array as KeyWindow deprecated
if #available(iOS 13.0, *) {
let window = UIApplication.shared.windows.first
let topPadding = window.safeAreaInsets.top
let bottomPadding = window.safeAreaInsets.bottom
}
In Swift - iOS 15.0+
// Use the keyWindow of the currentScene
if #available(iOS 15.0, *) {
let window = UIApplication.shared.currentScene?.keyWindow
let topPadding = window.safeAreaInsets.top
let bottomPadding = window.safeAreaInsets.bottom
}
Upvotes: 566
Reputation: 61
Nice and simple
struct ContentView: View {
var body: some View {
let safeAreaInsets = UIApplication.shared.windows.first?.safeAreaInsets ?? .zero
let safeAreaTop = safeAreaInsets.top
let safeAreaTop = safeAreaInsets.bottom
}
}
Upvotes: 0
Reputation: 1398
All of the answers here are helpful, Thanks to everyone who offered help.
However as i see that that the safe area topic is a little bit confused which won’t appear to be well documented.
So i will summarize it here as mush as possible to make it easy to understand safeAreaInsets
, safeAreaLayoutGuide
and LayoutGuide
.
In iOS 7, Apple introduced the topLayoutGuide
and bottomLayoutGuide
properties in UIViewController
,
They allowed you to create constraints to keep your content from being hidden by UIKit bars like the status, navigation or tab bar
It was possible with these layout guides to specify constraints on content,
avoiding it to be hidden by top or bottom navigation elements (UIKit bars, status bar, nav or tab bar…).
So for example if you wanna make a tableView starts from the top screen you have done something like that:
self.tableView.contentInset = UIEdgeInsets(top: -self.topLayoutGuide.length, left: 0, bottom: 0, right: 0)
In iOS 11 Apple has deprecated these properties replacing them with a single safe area layout guide
Safe area according to Apple
Safe areas help you place your views within the visible portion of the overall interface. UIKit-defined view controllers may position special views on top of your content. For example, a navigation controller displays a navigation bar on top of the underlying view controller’s content. Even when such views are partially transparent, they still occlude the content that is underneath them. In tvOS, the safe area also includes the screen’s overscan insets, which represent the area covered by the screen’s bezel.
Below, a safe area highlighted in iPhone 8 and iPhone X-series:
The safeAreaLayoutGuide
is a property of UIView
To get the height of safeAreaLayoutGuide
:
extension UIView {
var safeAreaHeight: CGFloat {
if #available(iOS 11, *) {
return safeAreaLayoutGuide.layoutFrame.size.height
}
return bounds.height
}
}
That will return the height of the Arrow in your picture.
Now, what about getting the top "notch" and bottom home screen indicator heights?
Here we will use the safeAreaInsets
The safe area of a view reflects the area not covered by navigation bars, tab bars, toolbars, and other ancestors that obscure a view controller's view. (In tvOS, the safe area reflects the area not covered by the screen's bezel.) You obtain the safe area for a view by applying the insets in this property to the view's bounds rectangle. If the view is not currently installed in a view hierarchy, or is not yet visible onscreen, the edge insets in this property are 0.
The following will show the unsafe area and there distance from edges on iPhone 8 and one of iPhone X-Series.
Now, if navigation bar added
So, now how to get the unsafe area height? we will use the safeAreaInset
Here are to solutions however they differ in an important thing,
First One:
self.view.safeAreaInsets
That will return the EdgeInsets, you can now access the top and the bottom to know the insets,
Second One:
UIApplication.shared.windows.first{$0.isKeyWindow }?.safeAreaInsets
The first one you are taking the view insets, so if there a navigation bar it will be considered , however the second one you are accessing the window's safeAreaInsets so the navigation bar will not be considered
Upvotes: 35
Reputation: 478
For iPhone 14 devices use like following.
let app = UIApplication.shared
var statusBarHeight: CGFloat = 0.0
let window = app.windows.filter {$0.isKeyWindow}.first
statusBarHeight = window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
let topPadding = window?.safeAreaInsets.top ?? 0.0
statusBarHeight = statusBarHeight >= topPadding ? statusBarHeight:topPadding
Upvotes: 0
Reputation: 1399
Or using SwiftUI GeometryReader api.
struct V: View {
var body: some View {
GeometryReader { geometry in
Text("\(geometry.safeAreaInsets.top)")
Text("\(geometry.safeAreaInsets.bottom)")
}
}
}
Code might not work, but illustrates the idea.
Upvotes: 2
Reputation: 819
This works for the entire view life cycle as a simple 2-line solution in Swift:
let top = UIApplication.shared.windows[0].safeAreaInsets.top
let bottom = UIApplication.shared.windows[0].safeAreaInsets.bottom
I personally needed it in the viewDidLoad
and view.safeAreaInsets
isn't calculated yet.
Upvotes: 7
Reputation: 19672
Here's a free function based on other answers that should be callable once your rootController is layed out from anywhere. You can use it as a free standing function.
func safeAreaInsets() -> UIEdgeInsets? {
(UIApplication
.shared
.keyWindow?
.rootViewController)
.flatMap {
if #available(iOS 11.0, *) {
return $0.view.safeAreaInsets
} else {
return .init(
top: $0.topLayoutGuide.length,
left: .zero,
bottom: $0.bottomLayoutGuide.length,
right: .zero
)
}
}
}
Upvotes: 0
Reputation: 433
For iOS 13+/Swift 5, nothing else here worked for me but this:
if #available(iOS 13.0, *) {
topPadding = UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0
bottomPadding = UIApplication.shared.windows.first?.safeAreaInsets.bottom ?? 0
}
Upvotes: 1
Reputation: 11
Here is a simple answer to find safe area height for all iphone
let window = UIApplication.shared.windows[0]
let SafeAreaHeight = window.safeAreaLayoutGuide.layoutFrame.size.height
Upvotes: -1
Reputation: 663
extension UIViewController {
var topbarHeight: CGFloat {
return
(view.window?.safeAreaInsets.top ?? 0) +
(view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0.0) +
(self.navigationController?.navigationBar.frame.height ?? 0.0)
}
}
Upvotes: 0
Reputation: 321
Swift 4
if let window = UIApplication.shared.windows.first {
let topPadding = window.safeAreaInsets.top
let bottomPadding = window.safeAreaInsets.bottom
}
Use from class
class fitToTopInsetConstraint: NSLayoutConstraint {
override func awakeFromNib() {
if let window = UIApplication.shared.windows.first {
let topPadding = window.safeAreaInsets.top
self.constant += topPadding
}
}
}
class fitToBottomInsetConstraint: NSLayoutConstraint {
override func awakeFromNib() {
if let window = UIApplication.shared.windows.first {
let bottomPadding = window.safeAreaInsets.bottom
self.constant += bottomPadding
}
}
}
You will see safe area padding when you build your application.
Upvotes: 0
Reputation: 1622
For those of you who change to landscape mode, you gotta make sure to use viewSafeAreaInsetsDidChange after the rotation to get the most updated values:
private var safeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
override func viewSafeAreaInsetsDidChange() {
if #available(iOS 11.0, *) {
safeAreaInsets = UIApplication.shared.keyWindow!.safeAreaInsets
}
}
Upvotes: 0
Reputation: 135
Swift 5 Extension
This can be used as a Extension and called with: UIApplication.topSafeAreaHeight
extension UIApplication {
static var topSafeAreaHeight: CGFloat {
var topSafeAreaHeight: CGFloat = 0
if #available(iOS 11.0, *) {
let window = UIApplication.shared.windows[0]
let safeFrame = window.safeAreaLayoutGuide.layoutFrame
topSafeAreaHeight = safeFrame.minY
}
return topSafeAreaHeight
}
}
Extension of UIApplication is optional, can be an extension of UIView or whatever is preferred, or probably even better a global function.
Upvotes: 3
Reputation: 12405
Swift 4, 5
To pin a view to a safe area anchor using constraints can be done anywhere in the view controller's lifecycle because they're queued by the API and handled after the view has been loaded into memory. However, getting safe-area values requires waiting toward the end of a view controller's lifecycle, like viewDidLayoutSubviews()
.
This plugs into any view controller:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let topSafeArea: CGFloat
let bottomSafeArea: CGFloat
if #available(iOS 11.0, *) {
topSafeArea = view.safeAreaInsets.top
bottomSafeArea = view.safeAreaInsets.bottom
} else {
topSafeArea = topLayoutGuide.length
bottomSafeArea = bottomLayoutGuide.length
}
// safe area values are now available to use
}
I prefer this method to getting it off of the window (when possible) because it’s how the API was designed and, more importantly, the values are updated during all view changes, like device orientation changes.
However, some custom presented view controllers cannot use the above method (I suspect because they are in transient container views). In such cases, you can get the values off of the root view controller, which will always be available anywhere in the current view controller's lifecycle.
anyLifecycleMethod()
guard let root = UIApplication.shared.keyWindow?.rootViewController else {
return
}
let topSafeArea: CGFloat
let bottomSafeArea: CGFloat
if #available(iOS 11.0, *) {
topSafeArea = root.view.safeAreaInsets.top
bottomSafeArea = root.view.safeAreaInsets.bottom
} else {
topSafeArea = root.topLayoutGuide.length
bottomSafeArea = root.bottomLayoutGuide.length
}
// safe area values are now available to use
}
Upvotes: 113
Reputation: 7283
To get the height between the layout guides you just do
let guide = view.safeAreaLayoutGuide
let height = guide.layoutFrame.size.height
So full frame height = 812.0
, safe area height = 734.0
Below is the example where the green view has frame of guide.layoutFrame
Upvotes: 129
Reputation: 1061
Swift 5, Xcode 11.4
`UIApplication.shared.keyWindow`
It will give deprecation warning. ''keyWindow' was deprecated in iOS 13.0: Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes' because of connected scenes. I use this way.
extension UIView {
var safeAreaBottom: CGFloat {
if #available(iOS 11, *) {
if let window = UIApplication.shared.keyWindowInConnectedScenes {
return window.safeAreaInsets.bottom
}
}
return 0
}
var safeAreaTop: CGFloat {
if #available(iOS 11, *) {
if let window = UIApplication.shared.keyWindowInConnectedScenes {
return window.safeAreaInsets.top
}
}
return 0
}
}
extension UIApplication {
var keyWindowInConnectedScenes: UIWindow? {
return windows.first(where: { $0.isKeyWindow })
}
}
Upvotes: 12
Reputation: 5257
A more rounded approach
import SnapKit
let containerView = UIView()
containerView.backgroundColor = .red
self.view.addSubview(containerView)
containerView.snp.remakeConstraints { (make) -> Void in
make.width.top.equalToSuperView()
make.top.equalTo(self.view.safeArea.top)
make.bottom.equalTo(self.view.safeArea.bottom)
}
extension UIView {
var safeArea: ConstraintBasicAttributesDSL {
if #available(iOS 11.0, *) {
return self.safeAreaLayoutGuide.snp
}
return self.snp
}
var isIphoneX: Bool {
if #available(iOS 11.0, *) {
if topSafeAreaInset > CGFloat(0) {
return true
} else {
return false
}
} else {
return false
}
}
var topSafeAreaInset: CGFloat {
let window = UIApplication.shared.keyWindow
var topPadding: CGFloat = 0
if #available(iOS 11.0, *) {
topPadding = window?.safeAreaInsets.top ?? 0
}
return topPadding
}
var bottomSafeAreaInset: CGFloat {
let window = UIApplication.shared.keyWindow
var bottomPadding: CGFloat = 0
if #available(iOS 11.0, *) {
bottomPadding = window?.safeAreaInsets.bottom ?? 0
}
return bottomPadding
}
}
Upvotes: 0
Reputation: 311
In iOS 11 there is a method that tells when the safeArea has changed.
override func viewSafeAreaInsetsDidChange() {
super.viewSafeAreaInsetsDidChange()
let top = view.safeAreaInsets.top
let bottom = view.safeAreaInsets.bottom
}
Upvotes: 9
Reputation: 1109
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
CGFloat fBottomPadding = window.safeAreaInsets.bottom;
Upvotes: 2
Reputation: 30801
safeAreaLayoutGuide When the view is visible onscreen, this guide reflects the portion of the view that is not covered by navigation bars, tab bars, toolbars, and other ancestor views. (In tvOS, the safe area reflects the area not covered the screen's bezel.) If the view is not currently installed in a view hierarchy, or is not yet visible onscreen, the layout guide edges are equal to the edges of the view.
Then to get the height of the red arrow in the screenshot it's:
self.safeAreaLayoutGuide.layoutFrame.size.height
Upvotes: 3
Reputation: 11
Objective-C Who had the problem when keyWindow is equal to nil. Just put the code above in viewDidAppear (not in viewDidLoad)
Upvotes: 1
Reputation: 14093
None of the other answers here worked for me, but this did.
var topSafeAreaHeight: CGFloat = 0
var bottomSafeAreaHeight: CGFloat = 0
if #available(iOS 11.0, *) {
let window = UIApplication.shared.windows[0]
let safeFrame = window.safeAreaLayoutGuide.layoutFrame
topSafeAreaHeight = safeFrame.minY
bottomSafeAreaHeight = window.frame.maxY - safeFrame.maxY
}
Upvotes: 34
Reputation: 9296
I'm working with CocoaPods frameworks and in case UIApplication.shared
is unavailable then I use safeAreaInsets
in view's window
:
if #available(iOS 11.0, *) {
let insets = view.window?.safeAreaInsets
let top = insets.top
let bottom = insets.bottom
}
Upvotes: 2