使用嵌入式视图了解自动布局生命周期和顺序

Posted

技术标签:

【中文标题】使用嵌入式视图了解自动布局生命周期和顺序【英文标题】:Understanding Auto Layout lifecycle and sequence with embedded views 【发布时间】:2020-07-24 02:22:00 【问题描述】:

我有以下视图层次结构:

ViewController: UIViewController 
|
+--- cardViewController: CardViewController (subclass of UIViewController)
     |
     +--- containerView: UIView
          |
          +--- menuTableView: MenuTableView (subclass of UITableView)
          

CardViewController 通过以下代码(从 ViewController 调用)显示在 ViewController 的视图中:

  let cardViewController = CardViewController(startWithRoundedCorners: true)
  self.addChild(cardViewController)
  self.view.addSubview(cardViewController.view)
  cardViewController.showCard()

CardViewController 中,containerView 对视图的顶部、前导、尾随和底部锚点有约束。

反过来,menuTableView 也对containerView 的顶部前导、尾随和底部锚点有限制。

问题是这样的。 menuTableView.contentSize.height 仅在通过cellForRowAt 加载 tableView 单元格后计算。说得通。直到那个时间点,它看起来只是根据行数 * 44(默认大小)来估计 tableView 的高度。所以如果我检查CardViewController: viewDidLayoutSubviews() 中的menuTableView.contentSize.height,我会返回264.0

我已经捕获了一些打印语句(一些被覆盖的方法带有“开始”和“完成”)来尝试计算出事情的运行顺序:

CardViewController: setuptableView(): started 
MenuTableView: convenience init(): started 
MenuTableView: override init(): started 
MenuTableView: numberOfRowsInSection: 
MenuTableView: override init(): self.frame.height: 0.0 
MenuTableView: override init(): self.contentSize.height: 0.0 
MenuTableView: override init(): finished 
MenuTableView: convenience init(): self.frame.height: 0.0 
MenuTableView: convenience init(): self.contentSize.height: 0.0 
MenuTableView: convenience init(): finished 
CardViewController: setuptableView(): finished
MenuTableView: numberOfRowsInSection: 
CardViewController: showCard(): self.frame.height: 0.0 
CardViewController: showCard(): self.contentSize.height: 264.0 
CardViewController: updateViewConstraints(): started 
CardViewController: updateViewConstraints(): finished  
CardViewController: viewWillLayoutSubviews(): started 
CardViewController: viewDidLayoutSubviews(): self.frame.height: 0.0 
CardViewController: viewDidLayoutSubviews(): self.contentSize.height: 264.0 
CardViewController: viewWillLayoutSubviews(): finished 
CardViewController: viewDidLayoutSubviews(): started  
CardViewController: viewDidLayoutSubviews(): self.frame.height: 0.0 
CardViewController: viewDidLayoutSubviews(): self.contentSize.height: 264.0 
CardViewController: viewDidLayoutSubviews(): finished 
MenuTableView: numberOfRowsInSection: 
MenuTableView: layoutSubviews(): started 
MenuTableView: numberOfRowsInSection: 
MenuTableView: cellForRowAt indexPath: [0, 0] 
MenuTableView: cellForRowAt indexPath: [0, 1] 
MenuTableView: cellForRowAt indexPath: [0, 2] 
MenuTableView: cellForRowAt indexPath: [0, 3] 
MenuTableView: cellForRowAt indexPath: [0, 4] 
MenuTableView: cellForRowAt indexPath: [0, 5] 
MenuTableView: layoutSubviews(): self.frame.height: 264.0 
MenuTableView: layoutSubviews(): self.contentSize.height: 306.0 
MenuTableView: layoutSubviews(): finished 
MenuTableView: layoutSubviews(): started 
MenuTableView: layoutSubviews(): self.frame.height: 264.0 
MenuTableView: layoutSubviews(): self.contentSize.height: 306.0 
MenuTableView: layoutSubviews(): finished

CardViewController: showCard() 方法的代码是:

UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 1.0, options: .curveEaseInOut, animations:  [weak self] in
    let cardHeight = (self?.menuTableView.contentSize.height)! + self!.cardHandleAreaHeight
    self?.view.frame = CGRect(x: 0, y: (self?.parent?.view.frame.height)! - cardHeight, width: (self?.parent?.view.frame.width)!, height: cardHeight)
    
    , completion: nil)

所以据我所知,问题在于menuTableView.layoutSubviews 被调用,并且tableView.contentSize.height 正确地位于306.0。但是到了这个阶段,父 viewController (CardViewController) 已经完成了它的viewWillLayoutSubviewsviewDidLayoutSubviews

问题

当子视图实际上仍在布置它们的视图时,父级如何完成其​​子视图的布置?根据 Apple 的文档,他们说自动布局循环是:

    更新约束(自下而上,从子视图到父视图) 布局(自上而下) 绘图/显示

如果改变内部内容大小,在这种情况下,tableView 会导致自动布局约束发生更新,我不知道为什么这个循环不起作用。我玩过很多选项,有不同的触发方式,但我希望了解自动布局引擎,所以我不再遇到这些问题。

其他信息

有趣的是,如果我第二次调用cardViewController.showCard(),那么它是正确的。额外的打印日志是:

CardViewController: showCard(): started
CardViewController: showCard(): self.frame.height: 278.0
CardViewController: showCard(): self.contentSize.height: 306.0
CardViewController: showCard(): finished
CardViewController: viewWillLayoutSubviews(): started
CardViewController: viewDidLayoutSubviews(): self.frame.height: 278.0
CardViewController: viewDidLayoutSubviews(): self.contentSize.height: 306.0
CardViewController: viewWillLayoutSubviews(): finished
CardViewController: viewDidLayoutSubviews(): started 
CardViewController: viewDidLayoutSubviews(): self.frame.height: 278.0
CardViewController: viewDidLayoutSubviews(): self.contentSize.height: 306.0
CardViewController: viewDidLayoutSubviews(): finished
MenuTableView: layoutSubviews(): started
MenuTableView: layoutSubviews(): self.frame.height: 306.0
MenuTableView: layoutSubviews(): self.contentSize.height: 306.0
MenuTableView: layoutSubviews(): finished

很高兴让项目文件可用,如果这更容易的话。

【问题讨论】:

这是很多信息 - 但并不完全清楚。您是否尝试根据其.contentSize 设置表格视图的高度? 不,相反。 tableview 的高度应该基于它的内容。 好的 - 是一样的。你想要一个非滚动的表格视图吗?所以它会根据内容调整自己的大小?看看这个:***.com/a/57995132/6257435 ...如果你使用那个子类表视图,你可以像使用多行一样使用它UILabel 谢谢。我已经尝试过了,但它不起作用。它确实会导致 CardViewController.viewDidLayoutSubviews 再次被调用,但仍然无法呈现正确的结果。 我应该补充一点,根据我的第一批打印语句,tableView 的大小是正确的(它的 contentSize.height 是在 306 处计算的)。但我认为的问题是它没有触发父视图控制器 - CardViewController 中的任何自动布局方法。我不明白为什么不... 【参考方案1】:

抱歉,我无法添加评论 yet 所以我试着用这个answer section 作为问题。

你把这些放在哪里了?

let cardViewController = CardViewController(startWithRoundedCorners: true)
  self.addChild(cardViewController)
  self.view.addSubview(cardViewController.view)
  cardViewController.showCard()

【讨论】:

我会更新问题,但它是从主 ViewController 调用的。

以上是关于使用嵌入式视图了解自动布局生命周期和顺序的主要内容,如果未能解决你的问题,请参考以下文章

Activity的生命周期及常见回调顺序

IOS / Autolayout:在生命周期中为代码中创建的元素设置约束

vue 生命周期 和 生命周期的执行顺序

vue3 生命周期

如何让自定义视图观察包含片段的生命周期事件而不是活动?

JetpackViewModel 架构组件 ( 视图 View 和 数据模型 Model | ViewModel 作用 | ViewModel 生命周期 | 代码示例 | 使用注意事项 )