获取安全区域插入的顶部和底部高度

Get safe area inset top and bottom heights

在新的 iPhone X 上,获得不安全区域的顶部和底部高度的最正确方法是什么?

要获得布局指南之间的高度,您只需这样做

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

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

下面是绿色视图的框架为 guide.layoutFrame

的示例

试试这个:

Objective C

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

Swift

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 排队并在视图加载到内存后处理。但是,获取 safe-area 值需要等待视图控制器的生命周期结束,例如 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
}

比起 window(如果可能),我更喜欢这种方法,因为它是 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
}

None 这里的其他答案对我有用,但这个确实有用。

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-CkeyWindow 等于 nil 时,谁遇到了问题。 只需将上面的代码放在 viewDidAppear 中(不在 viewDidLoad 中)

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

然后获取屏幕截图中红色箭头的高度为:

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

在iOS11中有一个方法可以告诉safeArea何时发生变化。

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

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

然而,据我所知,安全区域主题有点混乱,似乎没有很好的记录。

所以我会在这里尽可能地总结一下,以便于理解safeAreaInsetssafeAreaLayoutGuideLayoutGuide

在iOS7中,Apple在UIViewController中引入了topLayoutGuidebottomLayoutGuide属性, 它们允许您创建约束以防止您的内容被状态栏、导航栏或标签栏等 UIKit 栏隐藏 这些布局指南可以指定对内容的限制, 避免它被顶部或底部导航元素(UIKit 栏、状态栏、导航栏或标签栏……)隐藏。

因此,例如,如果您想从顶部屏幕开始创建一个 tableView,您已经做了类似的事情:

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

在 iOS 11 中,Apple 已弃用这些属性,将其替换为单个安全区域布局指南

根据 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.

下面,iPhone 8 和 iPhone X 系列中突出显示的安全区域:

safeAreaLayoutGuideUIView 的 属性

获取safeAreaLayoutGuide的高度:

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

这将 return 您图片中箭头的高度。

现在,如何获得顶部 "notch" 和底部主屏幕指示器高度?

这里我们将使用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.

以下将显示不安全区域以及与 iPhone 8 和 iPhone X 系列之一的边缘的距离。

现在,如果添加了导航栏

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

以下是解决方案,但它们在一个重要方面有所不同,

第一个:

self.view.safeAreaInsets

这将 return EdgeInsets,您现在可以访问顶部和底部以了解插图,

第二个一:

UIApplication.shared.windows.first{[=13=].isKeyWindow }?.safeAreaInsets

您正在使用视图插图的第一个,因此如果有导航栏,它将被考虑,但是第二个您正在访问 window 的 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
    }
}

Swift5,Xcode11.4

`UIApplication.shared.keyWindow` 

它将给出弃用警告。 ''keyWindow' 在 iOS 13.0 中被弃用:不应该用于支持多个场景的应用程序,因为它 returns 一个跨所有连接场景的键 window 因为连接场景。我是这样用的

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: { [=11=].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
    }
}

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

Swift 4

if let window = UIApplication.shared.windows.first {
    
    let topPadding = window.safeAreaInsets.top
    let bottomPadding = window.safeAreaInsets.bottom
    
}

使用来自 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
            
        }
        
    }
}

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

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 [=10=].view.safeAreaInsets
            } else {
                return .init(
                    top: [=10=].topLayoutGuide.length,
                    left: .zero,
                    bottom: [=10=].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

我个人在viewDidLoad需要它,view.safeAreaInsets还没有计算出来。