在新的 iPhone X 上,获取不安全区域的顶部和底部高度的最合适方法是什么?
21 回答
尝试这个 :
在目标 C
if (@available(iOS 11.0, *)) {
UIWindow *window = UIApplication.sharedApplication.windows.firstObject;
CGFloat topPadding = window.safeAreaInsets.top;
CGFloat bottomPadding = window.safeAreaInsets.bottom;
}
在斯威夫特
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
}
斯威夫特 4, 5
使用约束将视图固定到安全区域锚点可以在视图控制器生命周期的任何地方完成,因为它们由 API 排队并在视图加载到内存后处理。但是,获取安全区域值需要等待视图控制器生命周期的结束,例如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
}
我更喜欢这种方法而不是将其从窗口中移除(如果可能的话),因为它是 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
}
这里没有其他答案对我有用,但确实如此。
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
}
这里的所有答案都很有帮助,感谢所有提供帮助的人。
但是,正如我看到的那样,安全区域主题有点混乱,似乎没有很好的记录。
因此,我将在这里尽可能地总结一下,以使其易于理解safeAreaInsets
,safeAreaLayoutGuide
并且LayoutGuide
。
在 iOS 7 中,Apple 在 中引入了topLayoutGuide
和bottomLayoutGuide
属性UIViewController
,它们允许您创建约束以防止您的内容被 UIKit 栏(如状态、导航或标签栏)隐藏。这些布局指南可以指定内容的约束,避免它被顶部或底部导航元素(UIKit 栏、状态栏、导航或标签栏……)隐藏。
因此,例如,如果您想从顶部屏幕开始创建一个 tableView,您可以执行以下操作:
self.tableView.contentInset = UIEdgeInsets(top: -self.topLayoutGuide.length, left: 0, bottom: 0, right: 0)
在 iOS 11 中,Apple 已弃用这些属性,并用单个安全区域布局指南替换它们
Apple 规定的安全区域
安全区域可帮助您将视图放置在整个界面的可见部分中。UIKit 定义的视图控制器可能会将特殊视图放置在您的内容之上。例如,导航控制器在底层视图控制器的内容之上显示一个导航栏。即使这些视图是部分透明的,它们仍然会遮挡其下方的内容。在 tvOS 中,安全区域还包括屏幕的过扫描插图,代表屏幕边框所覆盖的区域。
下面是 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 中,安全区域反映了屏幕边框未覆盖的区域。)您可以通过将此属性中的插入应用到视图的边界矩形来获得视图的安全区域。如果视图当前未安装在视图层次结构中,或者在屏幕上尚不可见,则此属性中的边缘插入为 0。
下面将显示 iPhone 8 和 iPhone X 系列之一的不安全区域以及与边缘的距离。
现在,如果添加了导航栏
那么,现在如何获得不安全区域的高度呢?我们将使用safeAreaInset
这是解决方案,但它们在一个重要方面有所不同,
第一:
self.view.safeAreaInsets
这将返回 EdgeInsets,您现在可以访问顶部和底部以了解插图,
第二个:
UIApplication.shared.windows.first{$0.isKeyWindow }?.safeAreaInsets
您正在获取视图插图的第一个,因此如果有一个导航栏将被考虑,但是您正在访问窗口的 safeAreaInsets 的第二个,因此不会考虑导航栏
斯威夫特 5,Xcode 11.4
`UIApplication.shared.keyWindow`
它将给出弃用警告。''keyWindow' 在 iOS 13.0 中已弃用:不应该用于支持多个场景的应用程序,因为它会在所有连接的场景中返回一个关键窗口,因为连接的场景。我用这种方式。
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: { $0.isKeyWindow })
}
}
在 iOS 11 中,有一种方法可以告诉 safeArea 何时发生了变化。
override func viewSafeAreaInsetsDidChange() {
super.viewSafeAreaInsetsDidChange()
let top = view.safeAreaInsets.top
let bottom = view.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
}
}
UIApplication 的扩展是可选的,可以是 UIView 或任何首选的扩展,甚至可能是一个更好的全局函数。
safeAreaLayoutGuide 当视图在屏幕上可见时,该指南反映了导航栏、标签栏、工具栏和其他祖先视图未覆盖的视图部分。(在 tvOS 中,安全区域反映了未覆盖屏幕边框的区域。)如果视图当前未安装在视图层次结构中,或者尚未在屏幕上可见,则布局指南边缘等于视图的边缘。
然后要获取屏幕截图中红色箭头的高度:
self.safeAreaLayoutGuide.layoutFrame.size.height
我正在使用 CocoaPods 框架,如果UIApplication.shared
不可用,我safeAreaInsets
会在视图中使用window
:
if #available(iOS 11.0, *) {
let insets = view.window?.safeAreaInsets
let top = insets.top
let bottom = insets.bottom
}
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
CGFloat fBottomPadding = window.safeAreaInsets.bottom;
对于 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)
}
}
对于 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
}
Objective-C 当keyWindow等于nil时谁有问题。只需将上面的代码放在viewDidAppear中(不在 viewDidLoad 中)
或者使用 SwiftUI GeometryReader api。
struct V: View {
var body: some View {
GeometryReader { geometry in
Text("\(geometry.geometry.safeAreaInsets.top)")
Text("\(geometry.geometry.safeAreaInsets.bottom)")
}
}
}
代码可能不起作用,但说明了这个想法。
斯威夫特 4
if let window = UIApplication.shared.windows.first {
let topPadding = window.safeAreaInsets.top
let bottomPadding = window.safeAreaInsets.bottom
}
从课堂上使用
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
}
}
}
构建应用程序时,您将看到安全区域填充。
更全面的方法
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
}
}
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)
}
}
对于那些更改为横向模式的人,您必须确保在旋转后使用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
}
}
这是一个基于其他答案的免费功能,一旦您的 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
)
}
}
}
这是为所有 iphone 找到安全区域高度的简单答案
let window = UIApplication.shared.windows[0]
let SafeAreaHeight = window.safeAreaLayoutGuide.layoutFrame.size.height