【问题标题】:Force Auto layout to update UIView frame correctly at viewDidLoad强制自动布局在 viewDidLoad 处正确更新 UIView 框架
【发布时间】:2018-02-24 02:29:21
【问题描述】:

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

我试图了解在 viewDidLoad 使用自动布局时。

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

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

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

经过更多研究,我发现在DispatchQueue.main.async { } 下扭曲此代码可以得到准确的结果。但是我不确定这是否是处理该问题的正确方法,或者我是否使用它导致了某种幕后问题。最终“工作”代码:

override func viewDidLoad() {
        super.viewDidLoad() 

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

}

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

【问题讨论】:

  • viewDidLoad 不是设置需要框架知识的东西的地方,这就是您遇到此问题的原因
  • @trapper 我知道这一点。由于特定情况,我需要解决这个问题(并且可以解决)。我只是确保我以正确的方式解决了它
  • 在视图生命周期的早期,你试图用框架做什么?
  • @DavidRönnqvist 我需要预加载一些依赖于子视图框架的 CIImages。我需要尽早预加载它们,以便为用户提供最佳体验(他可能会立即使用它们)
  • 另外,dispatch async 在这里为您提供“正确”值的唯一原因是它被安排在下一个运行循环中运行; 之后 viewWillAppearviewDidLayoutSubviews 已经运行。时间发生了变化,但不如将相同的代码放在这两种方法中的稳健方式(因为它依赖于时间 - 如果视图已加载但未添加为子视图,则情况并非如此) )

标签: ios swift uiview autolayout viewdidload


【解决方案1】:

你的问题和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 ,因为每次您的视图位于不应该首选的视图层次结构的顶部时都会调用 viewWillAppear)。
  2. 如果您要跳过自动布局,则可以以编程方式创建和维护视图。

希望这会有所帮助!

【讨论】:

  • 你应该看看这个问题stackoverflow.com/questions/14060002/…。我认为viewWillAppear 不是 (1) 的正确选择。
  • 同意,这也是我提到的原因。我将对其进行更精确的编辑。感谢您指出。
  • 这里好像有误会;)。我的意思是 在 ios6 中使用自动布局时,在布局子视图之前不会设置框架。在这些条件下获取帧数据的正确位置是在 viewController 方法viewDidLayoutSubviews 中。答案是 update (ios6)
  • 没关系,我明白,但现在没有人或几乎没有人在使用 ios6 或更低版本。
  • 不是ios6以下。意思是ios6及以上。
【解决方案2】:

正如@DavidRönnqvist 指出的那样

dispatch async 在这里为您提供“正确”值的原因是 它被安排在下一个运行循环中运行;在viewWillAppear之后 并且viewDidLayoutSubviews 已经运行。

示例

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

DispatchQueue.main.async 中的代码来自 viewDidLoad,甚至在 viewDidAppear 之后调用。因此,在 viewDidLoad 中使用 DispatchQueue.main.async 可以为您提供正确的框架,但它可能不是最早的。

回答

  • 如果您想尽早获得正确的帧,viewDidLayoutSubviews 是正确的选择。

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

【讨论】:

    【解决方案3】:

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

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

    会做吗?

    【讨论】:

      猜你喜欢
      • 2019-12-19
      • 2015-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多