UIView 中的 safeAreaInsets 在 iPhone X 上为 0

safeAreaInsets in UIView is 0 on an iPhone X

我正在更新我的应用程序以适应 iPhone X。除一个视图外,所有视图现在都工作正常。我有一个视图控制器,它呈现一个覆盖整个屏幕的自定义 UIView。在完成所有布局之前,我使用 UIScreen.main.bounds 找出视图的大小(我需要它来为 collectionView 放置正确的 itemSize)。我以为现在我可以做类似的事情 UIScreen.main.bounds.size.height - safeAreaInsets.bottom 以获得正确的可用尺寸。问题是,safeAreaInsets returns (0,0,0,0) 正在尝试 iPhone X(模拟器)。有任何想法吗?在其他视图中,我得到了 safeAreaInsets 的正确数字。

谢谢!

我已经找到了解决方案:我在视图的 init 中进行了所有实施。 safeAreaInsets 在 layoutSubviews()

中的大小正确

容器视图控制器的 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
  } 
}

我有一个视图,它是另一个视图中的子视图。 我发现我无法正确获取 safeAreaInsets,它总是 return 0,在 iPhoneX 的那个视图中,即使我将它放在 layoutSubviews 中。 最终的解决方案是我使用以下 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

        }

    }

}

我遇到了同样的问题。在我的例子中,我插入的视图将在调用 view.layoutIfNeeded() 后正确调整大小。 view.safeAreaInsets 是在这之后设置的,但只有 top 值是正确的。底部值仍然是 0(在 iPhone X 上)。

在试图弄清楚 safeAreaInsets 的正确设置点时,我在视图的 safeAreaInsetsDidChange() 方法上添加了一个断点。这被调用了多次,但只有当我在回溯中看到 CALayer.layoutSublayers() 时,该值才被正确设置。

所以我用 CALayer 的对应 view.layer.layoutIfNeeded() 替换了 view.layoutIfNeeded(),这导致 safeAreaInsets 立即正确设置,从而解决了我的问题问题。

TL;DR

替换

view.layoutIfNeeded()

来自

view.layer.layoutIfNeeded()

我也 运行 遇到过这个问题,试图向上移动视图以为 iPhone X 上的键盘让路。主视图的 safeAreaInsets 始终为 0,即使我知道在绘制屏幕时子视图已在此时布置。如上所述,我发现的一个解决方法是获取 keyWindow 并检查其安全区域插入。

Obj-C:

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

Swift:

let bottomInset = UIApplication.shared.keyWindow?.safeAreaInsets.bottom

然后您可以使用此值根据需要调整约束或查看框架。

我最近遇到了类似的问题,一旦触发 viewDidLoad,安全区域插图就会返回 (0, 0, 0, 0)。似乎它们的设置比视图加载的其余部分稍晚。

我通过覆盖 viewSafeAreaInsetsDidChange 并在其中进行布局来绕过它:

override func viewSafeAreaInsetsDidChange() {
 // ... your layout code here
} 
[UIApplication sharedApplication].keyWindow.safeAreaInsets return none zero

只需尝试 self.view.safeAreaInsets 而不是 UIApplication.shared.keyWindow?.safeAreaInsets 当通过应用程序 keyWindow 请求时,安全区域插图似乎无法填充 iOS 11.x.x 设备。

在 layoutSubviews 或 viewDidLayoutSubviews 之前,永远无法保证视图布局。永远不要依赖这些生命周期方法之前的大小。如果这样做,您将得到不一致的结果。

要计算安全区域safeAreaInsets,尝试在viewWIllAppear()中获取它,因为在didLoad()中视图还没有形成。 您将在 willAppear 中获得正确的插图!

我尝试在视图控制器中使用 "self.view.safeAreaInset"。首先,当我在控制器的生命周期方法中使用它时,它是一个 NSInsetZero "viewDidLoad",然后我从网上搜索它并得到正确的答案,日志是这样的:

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 的正确方法并使用它!

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
}

在我的例子中,我在 viewDidLoad() 中添加了一个 UICollectionView

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

不幸的是,在此阶段 safeAreaLayoutGuide 仍然为零。

我通过添加解决了它:

override func viewDidLayoutSubviews() {
    collectionView.frame = view.safeAreaLayoutGuide.layoutFrame
}

如果你不能子类化,你可以使用这个 UIView 扩展。

它给你一个 API 这样的:

view.onSafeAreaInsetsDidChange = { [unowned self] in
   self.updateSomeLayout()
}

该扩展使用对象关联添加 onSafeAreaInsetsDidChange 属性。然后调整 UIView.safeAreaInsetsDidChange() 方法来调用闭包(如果有的话)。

extension UIView {
    
    typealias Action = () -> Void
    
    var onSafeAreaInsetsDidChange: Action? {
        get {
            associatedObject(for: "onSafeAreaInsetsDidChange") as? Action
        }
        set {
            Self.swizzleSafeAreaInsetsDidChangeIfNeeded()
            set(associatedObject: newValue, for: "onSafeAreaInsetsDidChange")
        }
    }
    
    static var swizzled = false
    
    static func swizzleSafeAreaInsetsDidChangeIfNeeded() {
        guard swizzled == false else { return }
        swizzle(
            method: "safeAreaInsetsDidChange",
            originalSelector: #selector(originalSafeAreaInsetsDidChange),
            swizzledSelector: #selector(swizzledSafeAreaInsetsDidChange),
            for: Self.self
        )
        swizzled = true
    }
    
    @objc func originalSafeAreaInsetsDidChange() {
        // Original implementaion will be copied here.
    }
    
    @objc func swizzledSafeAreaInsetsDidChange() {
        originalSafeAreaInsetsDidChange()
        onSafeAreaInsetsDidChange?()
    }
}

它使用了一些帮助程序(参见 NSObject+Extensions.swift and NSObject+Swizzle.swift),但如果您直接使用 sizzling 和对象关联 APIs,您实际上并不需要它。