强制自动布局在 viewDidLoad 正确更新 UIView 框架

Force Auto layout to update UIView frame correctly at viewDidLoad

尽管它可能很简单,但我仍然发现自己在寻找正确的解决方案。

我试图了解找到 REAL UIView(或任何其他子视图)的 正确 方法是什么) 使用自动布局时 viewDidLoad 内的框架。

主要问题是在 viewDidLoad 中,视图没有应用它们的约束。我知道 "known" 这种情况的答案是

override func viewDidLoad() {
        super.viewDidLoad()
     view.layoutIfNeeded()
     stepContainer.layoutIfNeeded() // We need this Subview real frame!
     let realFrame = stepContainer.frame
}

但我发现它并不总是有效,而且有时会给出错误的帧(即不是显示的最终帧)。

经过更多研究后,我发现在 DispatchQueue.main.async { } 下扭曲此代码可提供准确的结果。但我不确定这是否是处理该问题的正确方法,或者我是否使用它引起了某种幕后问题。最终 "working" 代码:

override func viewDidLoad() {
        super.viewDidLoad() 

        DispatchQueue.main.async {
            self. stepContainer.layoutIfNeeded()
            print(self. stepContainer.frame) // Real frame..
        }

}

注意:我需要从viewDidLoad中找到真正的框架,请不要建议使用viewDidAppear/layoutSubviews等

阅读评论后,也许您可​​以尝试将您的视图控制器嵌入到另一个带有容器的视图控制器中,然后执行以下操作:

  • 在父视图控制器中添加一个变量:var viewSize : CGSize?
  • 在子视图控制器中添加一个变量:var parentViewSize : CGSize?
  • 在父视图控制器中,获取viewDidLayoutSubViews中的大小并存储它:viewSize = view.size
  • 在父视图控制器中,在 prepareForSegue 中发送您存储的大小:(destinationViewController as? MyViewController)?.parentViewSize = viewSize
  • 在子视图控制器中,在 viewDidLoad 中,您将访问具有良好值的 parentViewSize 变量

会做吗?

正如@DavidRönnqvist 指出的那样

The reason dispatch async gives you the "correct" value here is that it get scheduled to run in the next run loop; after viewWillAppear and viewDidLayoutSubviews has run.

例子

override func viewDidLoad() {
  super.viewDidLoad()
  DispatchQueue.main.async {
    print("DispatchQueue.main.async viewDidLoad")
  }
}

override func viewWillAppear(_ animated: Bool) {
  super.viewWillAppear(animated)

  print("viewWillAppear")
}

override func viewDidAppear(_ animated: Bool) {
  super.viewDidAppear(animated)

  print("viewDidAppear")
}

override func viewDidLayoutSubviews() {
  super.viewDidLayoutSubviews()

  print("viewDidLayoutSubviews")
}

viewWillAppear

viewDidLayoutSubviews

viewDidAppear

DispatchQueue.main.async viewDidLoad

来自 viewDidLoadDispatchQueue.main.async 中的代码甚至在 viewDidAppear 之后被调用。所以在 viewDidLoad 中使用 DispatchQueue.main.async 会给你正确的框架,但它不是尽可能早。

回答

  • 如果你想尽早获得正确的帧,viewDidLayoutSubviews是正确的地方。

  • 如果您必须将一些代码放入viewDidLoad,那么您的做法是正确的。似乎 DispatchQueue.main.async 是最好的方法。

你的问题和this problem类似,我的回答也会一样

不保证 viewDidLoad 中的框架与最终显示视图时的框架相同。 UIKit 在呈现之前根据将出现的上下文调整视图控制器视图的框架。为了更好地理解视图生命周期,您可以参考这张图片。

该图像将帮助您理解您的代码运行的原因。如图所示,加载视图后将调用 viewWillAppear,并且当您设置分派异步时,它会在执行 viewWillAppear 后异步添加到线程中。因此,一旦 viewWillAppear 被调用,您的框架就会根据您的视图进行更新,并且您会在调度异步中获得正确的框架。

参考资料: Image source

For more information about view life cycle you can visit this answer

所以,最后,您可以选择以下两个选项之一:

  1. 如果您想使用自动布局,请在 viewDidLayoutSubviews 或 viewWillAppear 中管理框架。 (在这些 viewDidLayoutSubview 中的任何一个中,都应该选择,因为每次您的视图出现在视图层次结构的顶部时都会调用 viewWillAppear,这不应该是首选)。
  2. 如果您打算跳过自动布局,那么您可以以编程方式创建和维护视图。

希望对您有所帮助!