ChatGPT解决这个技术问题 Extra ChatGPT

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

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

https://i.stack.imgur.com/hQXf7.png


K
Khuong

尝试这个 :

在目标 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
}

@KrutikaSonawala bottomPadding + 10(你的价值)
这似乎对我不起作用 - 打印出 UIApplication.shared.keyWindow?.safeAreaInsets 的值返回全 0。有任何想法吗?
我做到了,我可以限制任何视图的 safeAreaLayoutGuides,但直接打印出关键窗口的 safeAreaInsets 的值只会返回一个零矩形。这是有问题的,因为我需要手动约束已添加到键窗口但不在根视图控制器的视图层次结构中的视图。
对我来说,keyWindow 始终是 nil,因此我将其更改为 windows[0] 并从可选链接中删除了 ?,然后它就起作用了。
只有当控制器的视图已经出现时它才起作用。
K
Krishna Raj Salim

要获得布局指南之间的高度,您只需执行

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

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

下面是绿色视图具有 guide.layoutFrame 框架的示例

https://i.stack.imgur.com/oe5xe.png


谢谢那是我也在考虑的线索。我认为没有更可靠的解决方案。但是这样我只能得到完全不安全的高度,对吧?每个区域不是两个高度吗?
一旦布置了视图,这将为您提供正确的答案
实际上@user6788419 的答案看起来也不错,而且更短。
使用控制器的视图,我使用该代码获得了 812.0 的高度。所以我使用UIApplication.sharedApplication.keyWindow.safeAreaLayoutGuide.layoutFrame,它确实有安全框架。
@FerranMaylinch 不确定您在哪里检查高度,但我也有 812,结果是由于在视图可见之前检查了值。在这种情况下,高度将相同“如果视图当前未安装在视图层次结构中,或者尚未在屏幕上可见,则布局指南边缘等于视图的边缘”developer.apple.com/documentation/uikit/uiview/…
f
fake girlfriends

斯威夫特 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
}

请改为使用 let topSafeArea: CGFloat 声明!
避免使用 viewDidLayoutSubviews(可以多次调用),这是一个即使在 viewDidLoad 中也可以使用的解决方案:stackoverflow.com/a/53864017/7767664
@user924 那么当安全区域改变时这些值不会被更新(即双高状态栏)
@bsod 那么这会更好stackoverflow.com/a/3420550/7767664(因为它仅用于状态栏而不是其他视图)
或者,您可以使用 Apple 内置在视图控制器中的安全区域 API,而不是通过应用程序委托。
S
ScottyBlades

这里没有其他答案对我有用,但确实如此。

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
  }

您的代码将在视图控制器生命周期的任何部分工作。如果你想使用 view.safeAreaInsets.top,那么你需要确保你在 viewDidAppear 之后或之后调用它。在 viewDidLoad 中,您不会获得具有正确值的 view.safeAreaInsets.top,因为它尚未初始化。
迄今为止最好的答案
这也适用于我,对于我的情况,我在 UITableViewCellcontentView(子视图)中有 WKWebView,它的高度限制在应用程序从后台启动后没有正确计算。尝试使用 UIApplicationssafeAreaInsets 它没有返回正确的顶部和底部值。我什至尝试在 viewDidAppear 中重新加载表格视图,但效果不佳。谢谢。
好主意,使用 UIWindow 而不是 ViewController 属性,以便它在 viewDidLoad 中工作。 UIWindow 还有一个 safeAreaInsets 属性,因此您可以使用 UIApplication.shared.windows[0].safeAreaInsets.topUIApplication.shared.windows[0].safeAreaInsets.bottom 将此代码变成一行
m
mojtaba al moussawi

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

但是,正如我看到的那样,安全区域主题有点混乱,似乎没有很好的记录。

因此,我将在这里尽可能地总结一下,以便于理解 safeAreaInsetssafeAreaLayoutGuideLayoutGuide

在 iOS 7 中,Apple 在 UIViewController 中引入了 topLayoutGuidebottomLayoutGuide 属性,它们允许您创建约束以防止您的内容被 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 系列中突出显示的安全区域:

https://i.stack.imgur.com/VRzXq.png

safeAreaLayoutGuideUIView 的属性

要获得 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 系列之一的不安全区域以及与边缘的距离。

https://i.stack.imgur.com/pRXS0.png

https://i.stack.imgur.com/GwfzV.png

现在,如果添加了导航栏

https://i.stack.imgur.com/9XljA.png

https://i.stack.imgur.com/B4kDX.png

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

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

第一:

self.view.safeAreaInsets

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

第二个:

UIApplication.shared.windows.first{$0.isKeyWindow }?.safeAreaInsets

您正在获取视图插图的第一个,因此如果有一个导航栏将被考虑,但是您正在访问窗口的 safeAreaInsets 的第二个,因此不会考虑导航栏


u
user1039695

斯威夫特 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 })
    }
}

M
Martin

在 iOS 11 中,有一种方法可以告诉 safeArea 何时发生了变化。

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

完美答案🤙🏻
m
malhal

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

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

self.safeAreaLayoutGuide.layoutFrame.size.height

T
Theodore

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 或任何首选的扩展,甚至可能是一个更好的全局函数。


M
Michael

这适用于整个视图生命周期,在 Swift 中作为一个简单的 2 行解决方案:

let top    = UIApplication.shared.windows[0].safeAreaInsets.top
let bottom = UIApplication.shared.windows[0].safeAreaInsets.bottom

我个人在 viewDidLoad 中需要它,而 view.safeAreaInsets 尚未计算。


T
Tai Le

我正在使用 CocoaPods 框架,如果 UIApplication.shared 不可用,那么我在视图的 window 中使用 safeAreaInsets

if #available(iOS 11.0, *) {
    let insets = view.window?.safeAreaInsets
    let top = insets.top
    let bottom = insets.bottom
}

P
Peter
UIWindow *window = [[[UIApplication sharedApplication] delegate] window]; 
CGFloat fBottomPadding = window.safeAreaInsets.bottom;

L
Lorenzo Fiamingo

对于 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)
    }
}

I
Ilya

或者使用 SwiftUI GeometryReader api。

struct V: View {
    var body: some View {
        GeometryReader { geometry in
            Text("\(geometry.safeAreaInsets.top)")
            Text("\(geometry.safeAreaInsets.bottom)")
        }
    }
}


代码可能不起作用,但说明了这个想法。


D
DmitryShapkin

Objective-C 当 keyWindow 等于 nil 时谁有问题。只需将上面的代码放在 viewDidAppear 中(不在 viewDidLoad 中)


J
Japes

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

j
johndpope

更全面的方法

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

O
Oz Shabat

对于那些更改为横向模式的人,您必须确保在旋转后使用 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
        }
}

S
Saharat Sittipanya

斯威夫特 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
            
        }
        
    }
}

https://i.stack.imgur.com/AnVAx.png

https://i.stack.imgur.com/YbBVq.png

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


S
Serg Smyk
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)
    }
}

S
SmileBot

这是一个基于其他答案的免费功能,一旦您的 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
                )
            }
        }
}

S
Slava Rozhnev

这是为所有 iphone 找到安全区域高度的简单答案

let window = UIApplication.shared.windows[0]

let SafeAreaHeight = window.safeAreaLayoutGuide.layoutFrame.size.height

这与 this other answer 中的解决方案相同。