在 iPhone X 上,UIView 中的 safeAreaInsets 为 0。

82

我正在更新我的应用以适应iPhone X。除了一个视图,所有视图都运行良好。 我有一个视图控制器,它呈现一个自定义的UIView,覆盖整个屏幕。 之前我使用UIScreen.main.bounds在所有布局完成之前找到视图的大小(我需要为collectionView放置正确的itemSize),但现在我想做一些类似于UIScreen.main.bounds.size.height - safeAreaInsets.bottom 的操作来获取正确的可用空间大小。问题是,在iPhone X(模拟器)上尝试时,safeAreaInsets返回(0,0,0,0)。有什么想法吗? 在其他视图中,我得到了正确的safeAreaInsets数字。

谢谢!


4
无论何时何地,您都可以使用以下代码:if #available(iOS 11.0, tvOS 11.0, *) { return UIApplication.shared.delegate?.window??.safeAreaInsets != .zero } return falseSOURCE - Cœur
根据下面 Mars 的回答,我也发现唯一可靠的方法是在 viewDidLayoutSubviews() 或 viewDidAppear 中获取这些值。实际上,"override func viewSafeAreaInsetsDidChange()" 似乎是最好的选择。 - David H
14个回答

105

最近我遇到了一个类似的问题,当viewDidLoad被触发时,安全区域插图返回值为(0, 0, 0, 0)。看起来它们的设置时间比其他视图加载稍晚。

我通过重写 viewSafeAreaInsetsDidChange 方法并在其中进行布局来绕过此问题:

override func viewSafeAreaInsetsDidChange() {
 // ... your layout code here
} 

1
同上 - 这是正确的 - 尽管这种情况有点愚蠢 - Jonathan Plackett
4
如果OP想在自定义视图而非视图控制器中执行此操作,则还可以使用UIView的方法safeAreaInsetsDidChange - Arda
1
如果您正在配置视图控制器中的单元格,则这非常完美。 - Jaap Weijland
1
我已经切换到横屏模式,这是唯一有效的答案! - Oz Shabat
1
运行得非常好。不确定在不知道这种方法的过去5年里我是如何生存的 :/ - Trev14

48

我已经找到了解决方案:我之前在视图的init中执行了所有实现。在layoutSubviews()中,safeAreaInsets的大小是正确的。


11
如果您在UIViewController中使用了view,那么您也可以在viewDidLayoutSubviews()中完成它,并且它应该能够像我一样正常工作。 - Natanel
2
注意在viewWillLayoutSubviews中,子视图的saveAreaInsets值可能会出现错误。 - Slav
你也可以在UIViewController.viewDidLayoutSubviews中完成它。 - Christopher Schardt

43

我也遇到了这个问题,尝试将视图上移以腾出 iPhone X 上的键盘空间。尽管我知道子视图已经被布局,因为屏幕已经被绘制出来,但主视图的safeAreaInsets始终为0。一个我找到的解决方法就是像上面提到的那样获取keyWindow并检查其安全区域插入值。

Obj-C:

CGFloat bottomInset = [UIApplication sharedApplication].keyWindow.safeAreaInsets.bottom;

Swift:


Swift是一种由苹果公司开发的编程语言,用于iOS、macOS和watchOS等平台上的应用程序开发。它旨在提供更安全、更快速和更易于使用的替代方案来替代Objective-C语言。Swift同时支持面向协议和面向对象编程范例,并为函数式编程提供了支持。
let bottomInset = UIApplication.shared.keyWindow?.safeAreaInsets.bottom

你可以使用这个值来根据需要调整约束或查看框架。


它在竖屏模式下工作。横屏模式没有顶部视图。 - Ning
只有这个解决方案对我起作用。有时候根本不会调用viewSafeAreaInsetsDidChange()。 - Murat Yasar
我应该在哪里添加 bottomInset 呢? - MikeMaus

21

我有一个视图,它是另一个视图中的子视图。 我发现即使将其放置在layoutSubviews中,在iPhoneX上,该视图中无法正确获取safeAreaInsets,它始终返回0。 最终的解决方法是使用以下UIScreen扩展来检测safeAreaInsets,这可以完美解决问题。

extension UIScreen {

    func widthOfSafeArea() -> CGFloat {

        guard let rootView = UIApplication.shared.keyWindow else { return 0 }

        if #available(iOS 11.0, *) {

            let leftInset = rootView.safeAreaInsets.left

            let rightInset = rootView.safeAreaInsets.right

            return rootView.bounds.width - leftInset - rightInset

        } else {

            return rootView.bounds.width

        }

    }

    func heightOfSafeArea() -> CGFloat {

        guard let rootView = UIApplication.shared.keyWindow else { return 0 }

        if #available(iOS 11.0, *) {

            let topInset = rootView.safeAreaInsets.top

            let bottomInset = rootView.safeAreaInsets.bottom

            return rootView.bounds.height - topInset - bottomInset

        } else {

            return rootView.bounds.height

        }

    }

}

虽然这段代码可能回答了问题,但是提供关于为什么和/或如何回答问题的额外上下文可以提高其长期价值。 - Donald Duck
谢谢您的建议。我已经编辑了我的答案。 - Shih Ken
1
我发现当手动添加子视图时(即使它们使用自动布局),在某些情况下,例如表视图控制器中,safeAreaInsets 始终为零。使用应用程序关键窗口返回正确的安全区域插图。做得好。 - Eneko Alonso
7
在只有状态栏(iPhone 6、7等)的iOS 11上,这个不起作用。它会返回顶部插入高度为0。 - Tony
你可以将它们设为静态的。 - Reimond Hill

15

我试图在一个视图控制器中使用"self.view.safeAreaInset"。首先,在控制器的生命周期方法"viewDidLoad"中使用它时,它是一个NSInsetZero,然后我从网上查找到了正确的答案,日志如下:

ViewController loadView() SafeAreaInsets :UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
ViewController viewDidLoad() SafeAreaInsets :UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
ViewController viewWillAppear() SafeAreaInsets :UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
ViewController viewDidLayoutSubviews() SafeAreaInsets :UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)
ViewController viewDidAppear() SafeAreaInsets :UIEdgeInsets(top: 44.0, left: 0.0, bottom: 34.0, right: 0.0)

因此,您可以选择正确的方法,根据需要使用safeAreaInset并确保其安全!


9

Swift iOS 11,12,13+

var insets : UIEdgeInsets = .zero

 override func viewDidLoad() {
     super.viewDidLoad()

     insets = UIApplication.shared.delegate?.window??.safeAreaInsets ?? .zero
     //Or you can use this
     insets = self.view.safeAreaInsets
}

3
我们知道为什么UIApplication.shared.keyWindow不再返回安全区域插图吗?如果我们不能确定这将在未来继续工作,使用UIApplication.shared.delegate?.window??.safeAreaInsets似乎很冒险。我猜测这是因为添加了场景,但它们直到iOS 13才出现,所以可能不是这个原因? - Greg Ellis

4

在我的情况下,我正在viewDidLoad()中添加一个UICollectionView。

collectionView = UICollectionView(frame: view.safeAreaLayoutGuide.layoutFrame, collectionViewLayout: createCompositionalLayout())

很遗憾,在这个阶段,safeAreaLayoutGuide仍然为零。
我通过添加以下内容来解决它:
override func viewDidLayoutSubviews() {
    collectionView.frame = view.safeAreaLayoutGuide.layoutFrame
}

2

在容器视图控制器的viewDidAppear(_:)方法中,将安全区域扩展到嵌入的子视图控制器,以便考虑视图中的内容。

请在此方法中进行修改,因为视图的安全区域插入值在视图添加到视图层次结构之前是不准确的。

override func viewDidAppear(_ animated: Bool) {

   if (@available(iOS 11, *)) {

     var newSafeArea = view.safeAreaInsets

     // Adjust the safe area to accommodate 
     //  the width of the side view.

     if let sideViewWidth = sideView?.bounds.size.width {
        newSafeArea.right += sideViewWidth
     }

    // Adjust the safe area to accommodate 
    //  the height of the bottom view.
    if let bottomViewHeight = bottomView?.bounds.size.height {
       newSafeArea.bottom += bottomViewHeight
    }

    // Adjust the safe area insets of the 
    //  embedded child view controller.
    let child = self.childViewControllers[0]
    child.additionalSafeAreaInsets = newSafeArea
  } 
}

2
我遇到了同样的问题。在我的情况下,调用view.layoutIfNeeded()后,我插入的视图将被正确地调整大小。 view.safeAreaInsets是在此之后设置的,但只有top值是正确的。底部值仍为0(这是在iPhone X上)。
当试图找出何时正确设置safeAreaInsets时,我在视图的safeAreaInsetsDidChange()方法上添加了断点。这被调用了多次,但只有当我在回溯中看到CALayer.layoutSublayers()时,该值才被正确设置。
因此,我用CALayer的对应项view.layer.layoutIfNeeded()替换了view.layoutIfNeeded(),这导致safeAreaInsets立即被正确设置,从而解决了我的问题。 TL;DR 替换
view.layoutIfNeeded()

by

view.layer.layoutIfNeeded()

如果您正在执行视图转换动画,则无法工作,也没有用处。 - Pedro Paulo Amorim

1
视图布局在layoutSubviews或viewDidLayoutSubviews方法调用之前是不确定的。在这些生命周期方法之前,不要依赖尺寸。如果这样做,您将得到不一致的结果。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接