设置Auto Layout约束时UI元素的Frame与Bounds未即时更新的原因
在创建Auto Layout约束时,你会发现刚给UI元素加完约束,它的frame和bounds并不会立刻更新——就像你示例里的情况:
NSLayoutConstraint.activate([ counterLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), counterLabel.topAnchor.constraint(equalTo: timeLabel.bottomAnchor, constant: view.frame.size.height/100), counterLabel.widthAnchor.constraint(equalToConstant: view.frame.size.width/1.2), gameButton.heightAnchor.constraint(equalToConstant: (view.bounds.size.height-counterLabel.frame.maxY)) // 这里不生效 ])
明明已经给counterLabel加了约束,它的frame却还是(0,0,0,0),导致gameButton的高度约束完全不对。但把gameButton的高度约束移到viewDidLayoutSubviews里就正常了,核心原因和iOS的布局流程有关:
约束激活只是标记布局待更新,不会立刻计算布局
调用NSLayoutConstraint.activate()时,系统只是把约束加入布局系统,标记当前布局为“需要更新”的状态,但不会马上执行实际的布局计算。这时候所有受约束元素的frame还是初始化时的默认值,自然拿不到正确的位置信息。系统会批量异步处理布局计算
iOS的布局计算是批量、异步执行的,通常会等当前代码块执行完,或者到下一个绘制周期前,才统一计算所有约束,更新子视图的frame和bounds。所以在激活约束的代码刚结束时访问frame,拿到的肯定是旧值。viewDidLayoutSubviews是布局完成后的回调
viewDidLayoutSubviews这个生命周期方法,会在系统完成所有布局计算、更新完所有子视图的frame/bounds之后触发。这时候counterLabel的frame已经是约束计算后的正确结果,用它来设置gameButton的高度约束,就能得到符合预期的布局。
如果不想等到这个方法,也可以手动调用view.layoutIfNeeded()强制系统立刻计算布局,但这种强制同步计算可能影响性能,非必要不推荐使用。
内容的提问来源于stack exchange,提问作者Amala Durigana

