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,您实际上并不需要它。
我正在更新我的应用程序以适应 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,您实际上并不需要它。