得到安全区域嵌入顶部和底部高度

最合适的方法是什么来获得不安全区域的顶部和底部高度?

enter image description here

259120 次浏览

要获得布局参考线之间的高度,只需这样做

let guide = view.safeAreaLayoutGuide
let height = guide.layoutFrame.size.height

所以full frame height = 812.0safe area height = 734.0

下面的例子中,绿色视图的帧为guide.layoutFrame

enter image description here

试试这个:

Objective - C

if (@available(iOS 11.0, *)) {
UIWindow *window = UIApplication.sharedApplication.windows.firstObject;
CGFloat topPadding = window.safeAreaInsets.top;
CGFloat bottomPadding = window.safeAreaInsets.bottom;
}

斯威夫特

if #available(iOS 11.0, *) {
let window = UIApplication.shared.keyWindow
let topPadding = window?.safeAreaInsets.top
let bottomPadding = window?.safeAreaInsets.bottom
}

Swift - iOS 13.0及以上

//使用windows数组中的第一个元素,KeyWindow已弃用

if #available(iOS 13.0, *) {
let window = UIApplication.shared.windows.first
let topPadding = window.safeAreaInsets.top
let bottomPadding = window.safeAreaInsets.bottom
}

Swift 4,5

使用约束将视图固定到安全区域锚点可以在视图控制器生命周期的任何地方完成,因为它们由API排队,并在视图加载到内存后进行处理。然而,获取安全区值需要等待视图控制器生命周期的结束,比如viewDidLayoutSubviews()

这将插入到任何视图控制器:

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
}

我更喜欢这种方法,而不是将它从窗口中删除(如果可能的话),因为这是API的设计方式,更重要的是,在所有视图更改期间,值都会更新,比如设备方向更改。

然而,一些自定义呈现的视图控制器不能使用上述方法(我怀疑是因为它们在瞬态容器视图中)。在这种情况下,你可以从根视图控制器获取值,它总是在当前视图控制器生命周期的任何地方可用。

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
}

我正在使用CocoaPods框架,如果UIApplication.shared不可用,那么我在视图的window中使用safeAreaInsets:

if #available(iOS 11.0, *) {
let insets = view.window?.safeAreaInsets
let top = insets.top
let bottom = insets.bottom
}

这里的其他答案对我都没用,但这个对我有用。

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
}

objective - c < >强 当keyWindow等于时,谁遇到了问题。 只要把上面的代码放在在viewDidAppear(不是在viewDidLoad)

< >强safeAreaLayoutGuide 当视图在屏幕上可见时,该指南反映了未被导航栏、选项卡栏、工具栏和其他祖先视图覆盖的视图部分。(在tvOS中,安全区域反映未覆盖屏幕边框的区域。)如果视图当前没有安装在视图层次结构中,或者在屏幕上还不可见,则布局指南边等于视图的边

然后在截图中获得红色箭头的高度:

self.safeAreaLayoutGuide.layoutFrame.size.height
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
CGFloat fBottomPadding = window.safeAreaInsets.bottom;

在iOS 11中,有一个方法告诉安全区域什么时候发生了变化。

override func viewSafeAreaInsetsDidChange() {
super.viewSafeAreaInsetsDidChange()
let top = view.safeAreaInsets.top
let bottom = view.safeAreaInsets.bottom
}

这里所有的答案都很有帮助,感谢所有提供帮助的人。

然而,正如我所看到的,安全区域的主题有点混乱,这似乎没有很好的记录。

因此,为了便于理解safeAreaInsetssafeAreaLayoutGuideLayoutGuide,我将在这里尽可能多地总结它。

在ios7中,苹果在UIViewController中引入了topLayoutGuidebottomLayoutGuide属性, 它们允许你创建约束,以防止你的内容被UIKit栏(如状态栏、导航栏或选项卡栏)隐藏 使用这些布局指南可以指定内容的约束, 避免它被顶部或底部的导航元素(UIKit栏,状态栏,导航或标签栏…)所隐藏

举个例子,如果你想让tableView从顶部屏幕开始你可以这样做

self.tableView.contentInset = UIEdgeInsets(top: -self.topLayoutGuide.length, left: 0, bottom: 0, right: 0)

在iOS 11中,苹果已经弃用了这些属性,取而代之的是单一的安全区域布局指南

苹果说这是安全区域

安全区域帮助您将视图放置在整个界面的可见部分中。uikit定义的视图控制器可以在你的内容顶部放置特殊的视图。例如,导航控制器在底层视图控制器内容的顶部显示一个导航条。即使这样的视图是部分透明的,它们仍然挡住了下面的内容。在tvOS中,安全区域还包括屏幕的扫描插入,它表示屏幕边框覆盖的区域。

下图为iPhone 8和iPhone x系列突出显示的安全区域:

enter image description here

safeAreaLayoutGuideUIView的属性

获取safeAreaLayoutGuide的高度:

extension UIView {
var safeAreaHeight: CGFloat {
if #available(iOS 11, *) {
return safeAreaLayoutGuide.layoutFrame.size.height
}
return bounds.height
}
}

这将返回图片中箭头的高度。

现在,如何获得最高的“等级”;和底部主屏幕指示高度?

这里我们将使用safeAreaInsets

视图的安全区域反映了未被导航栏、标签栏、工具栏和其他模糊视图控制器视图的祖先所覆盖的区域。(在tvOS中,安全区域反映的是屏幕边框未覆盖的区域。)通过将此属性中的insets应用于视图的边界矩形,可以获得视图的安全区域。如果视图当前没有安装在视图层次结构中,或者在屏幕上还不可见,则此属性中的边缘嵌入为0。

下面将显示iPhone 8和iPhone x系列的不安全区域及其与边缘的距离。

enter image description here

enter image description here

现在,如果导航栏添加

enter image description here

enter image description here

那么,现在如何求不安全区域高度呢?我们将使用safeAreaInset

这里有一些解决方案,但它们在一个重要的事情上有所不同,

第一个:

self.view.safeAreaInsets

这将返回EdgeInsets,你现在可以访问顶部和底部了解insets,

第二个:

UIApplication.shared.windows.first{$0.isKeyWindow }?.safeAreaInsets

第一个你使用的是视图insets,所以如果有一个导航栏,它将被考虑,然而第二个你访问的是窗口的safeAreaInsets,所以导航栏将不会被考虑

更全面的方法

  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
}
}

Swift 5, Xcode 11.4

`UIApplication.shared.keyWindow`

它将给出弃用警告。" keyWindow "在iOS 13.0中已弃用:不应该用于支持多场景的应用程序,因为它会在所有连接的场景中返回一个键窗口。我用这种方法。

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 })
}
}

Swift 5扩展

它可以用作扩展,并使用: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是可选的,可以是UIView的扩展或者任何你喜欢的,或者更好的全局函数。

对于那些切换到横向模式的人,你必须确保在旋转之后使用viewSafeAreaInsetsDidChange来获得最新的值:

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
}
}

斯威夫特4

if let window = UIApplication.shared.windows.first {
    

let topPadding = window.safeAreaInsets.top
let bottomPadding = window.safeAreaInsets.bottom
    

}

从课堂使用

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
            

}
        

}
}

enter image description here

enter image description here

在构建应用程序时,您将看到安全区域填充。

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)
}
}

SwiftUI:

代码

private struct SafeAreaInsetsKey: EnvironmentKey {
static var defaultValue: EdgeInsets {
UIApplication.shared.windows[0].safeAreaInsets.insets
}
}


extension EnvironmentValues {
    

var safeAreaInsets: EdgeInsets {
self[SafeAreaInsetsKey.self]
}
}


private extension UIEdgeInsets {
    

var insets: EdgeInsets {
EdgeInsets(top: top, leading: left, bottom: bottom, trailing: right)
}
}

使用

struct MyView: View {
    

@Environment(\.safeAreaInsets) private var safeAreaInsets
    

var body: some View {
Text("Ciao")
.padding(safeAreaInsets)
}
}

这里有一个简单的答案,可以找到所有iphone的安全区域高度

let window = UIApplication.shared.windows[0]


let SafeAreaHeight = window.safeAreaLayoutGuide.layoutFrame.size.height

对于iOS 13+/Swift 5,除了这个,没有其他方法适合我:

    if #available(iOS 13.0, *) {
topPadding = UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0
bottomPadding = UIApplication.shared.windows.first?.safeAreaInsets.bottom ?? 0
}

这里有一个基于其他答案的免费函数,当你的rootController从任何地方布局时,它都应该是可调用的。你可以把它当做一个独立的函数。

    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
)
}
}
}

或者使用SwiftUI GeometryReader api。

struct V: View {
var body: some View {
GeometryReader { geometry in
Text("\(geometry.safeAreaInsets.top)")
Text("\(geometry.safeAreaInsets.bottom)")
}
}
}




代码可能不起作用,但却说明了这个想法。

这适用于整个视图生命周期,在Swift中是一个简单的两行解决方案:

let top    = UIApplication.shared.windows[0].safeAreaInsets.top
let bottom = UIApplication.shared.windows[0].safeAreaInsets.bottom

我个人需要它在viewDidLoadview.safeAreaInsets尚未计算。

对于iPhone 14设备使用如下。

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