【问题标题】:Get safe area inset top and bottom heights获取安全区域插图顶部和底部高度
【发布时间】:2018-03-31 11:43:12
【问题描述】:

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

【问题讨论】:

    标签: ios iphone-x safearealayoutguide


    【解决方案1】:

    试试这个:

    目标 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] 并从可选链接中删除了 ?,然后它就起作用了。
    • 只有当控制器的视图已经出现时才有效。
    【解决方案2】:

    要获得你刚刚做的布局指南之间的高度

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

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

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

    【讨论】:

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

    斯威夫特 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,而不是通过应用程序委托。
    【解决方案4】:

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

    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 将此代码转换为一行
    【解决方案5】:

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

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

    所以我将在这里尽可能地总结一下,以便于理解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 系列中突出显示的安全区域:

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

    现在,如果添加了导航栏

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

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

    第一个:

    self.view.safeAreaInsets
    

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

    第二个:

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

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

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

      • 完美答案??
      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

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

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

          【讨论】:

            【解决方案10】:

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

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

            self.safeAreaLayoutGuide.layoutFrame.size.height
            

            【讨论】:

              【解决方案11】:
              UIWindow *window = [[[UIApplication sharedApplication] delegate] window]; 
              CGFloat fBottomPadding = window.safeAreaInsets.bottom;
              

              【讨论】:

                【解决方案12】:

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

                【讨论】:

                  【解决方案13】:

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

                  【讨论】:

                    【解决方案14】:

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

                    【讨论】:

                      【解决方案15】:

                      或者使用 SwiftUI GeometryReader api。

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

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

                      【讨论】:

                        【解决方案16】:

                        更全面的方法

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

                        【讨论】:

                          【解决方案17】:

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

                          【讨论】:

                            【解决方案18】:

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

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

                            【讨论】:

                              【解决方案19】:
                              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)
                                  }
                              }
                              

                              【讨论】:

                                【解决方案20】:

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

                                【讨论】:

                                  【解决方案21】:

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

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

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

                                  【讨论】:

                                    【解决方案22】:

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

                                    let window = UIApplication.shared.windows[0]
                                    
                                    let SafeAreaHeight = window.safeAreaLayoutGuide.layoutFrame.size.height
                                    

                                    【讨论】:

                                    猜你喜欢
                                    • 1970-01-01
                                    • 2018-04-28
                                    • 2018-02-22
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 1970-01-01
                                    相关资源
                                    最近更新 更多