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


当前回答

对于 SwiftUI:

Code

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

其他回答

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

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

所以我将在这里总结,尽可能让它容易理解safeAreaInsets, safeAreaLayoutGuide和LayoutGuide。

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

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

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

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

苹果说这是安全区域

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.

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

safeAreaLayoutGuide是UIView的一个属性

获取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系列的不安全区域及其与边缘的距离。

现在,如果导航栏添加

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

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

第一个:

self.view.safeAreaInsets

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

第二个:

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

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

这里有一个基于其他答案的免费函数,当你的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
                )
            }
        }
}
UIWindow *window = [[[UIApplication sharedApplication] delegate] window]; 
CGFloat fBottomPadding = window.safeAreaInsets.bottom;

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的扩展或者任何你喜欢的,或者更好的全局函数。

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

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