无intrinsic size的UIView仅约束单维度时Autolayout如何计算其尺寸?
Auto Layout单维度约束下的尺寸计算逻辑
当你给没有固有内容尺寸(intrinsic size)的视图仅约束一个维度时,Auto Layout会通过两趟约束求解流程完成尺寸计算:
- 第一趟自上而下传递可用尺寸:引擎会先求解已经明确的约束,比如你示例中给视图加的左右边距约束,父视图的宽度是已知的,所以这一趟就能计算出视图的固定宽度,赋值给视图的
bounds.size.width - 第二趟自下而上反馈所需尺寸:视图拿到确定的宽度后,内部计算适配该宽度的高度,再反馈给Auto Layout引擎,引擎用这个高度完成剩余约束(比如你示例中的垂直居中约束)的求解
手动排布子视图的实现方案
你可以根据自己的业务场景选择以下两种常用实现方式,不需要额外引入第三方工具:
方案1:重写layoutSubviews(最适合手动排布场景)
这是手动布局场景的首选方案,流程如下:
- Auto Layout第一趟求解完成后,会先给你的视图赋值好确定的宽度,再自动触发
layoutSubviews调用 - 在
layoutSubviews中直接读取已经计算完成的self.bounds.size.width作为排布基准,手动计算所有子视图的frame - 所有子视图排布完成后,计算整个容器需要的总高度,直接修改
self.bounds.size.height即可,修改后的值会自动同步给Auto Layout引擎完成剩余约束计算
参考代码示例(Swift):
class CustomContainerView: UIView { // 示例子视图 private let titleLabel = UILabel() private let contentImageView = UIImageView() private let actionButton = UIButton() // 自定义间距参数 private let padding: CGFloat = 16 private let itemSpacing: CGFloat = 12 override func layoutSubviews() { super.layoutSubviews() // 此处拿到的宽度已经是Auto Layout计算好的固定值 let availableWidth = self.bounds.width var currentY = padding // 手动排布标题 let titleWidth = availableWidth - padding * 2 let titleHeight = titleLabel.sizeThatFits(CGSize(width: titleWidth, height: CGFloat.greatestFiniteMagnitude)).height titleLabel.frame = CGRect(x: padding, y: currentY, width: titleWidth, height: titleHeight) currentY += titleHeight + itemSpacing // 手动排布图片:按宽高比16:9自适应 let imageHeight = (availableWidth - padding * 2) * 9 / 16 contentImageView.frame = CGRect(x: padding, y: currentY, width: availableWidth - padding * 2, height: imageHeight) currentY += imageHeight + itemSpacing // 手动排布按钮 actionButton.sizeToFit() actionButton.frame = CGRect(x: padding, y: currentY, width: actionButton.bounds.width, height: actionButton.bounds.height) currentY += actionButton.bounds.height + padding // 计算容器总高度,避免重复赋值触发循环调用 let totalHeight = currentY if self.bounds.height != totalHeight { self.bounds.size.height = totalHeight } } }
方案2:重写intrinsicContentSize(适合内容驱动尺寸的场景)
如果你的容器视图尺寸完全由内部内容决定,可以选择重写该属性,Auto Layout需要查询视图尺寸时会主动调用该属性的getter:
- getter中可以拿到当前已经确定的宽度,计算对应高度返回即可
- 内部内容变化时,调用
invalidateIntrinsicContentSize()即可触发Auto Layout重新计算尺寸
参考代码示例(Swift):
class CustomContainerView: UIView { private let padding: CGFloat = 16 private let itemSpacing: CGFloat = 12 override var intrinsicContentSize: CGSize { let availableWidth = self.bounds.width guard availableWidth > 0 else { return CGSize(width: UIView.noIntrinsicMetric, height: UIView.noIntrinsicMetric) } // 按宽度计算所有元素总高度,逻辑和方案1一致 let titleHeight = titleLabel.sizeThatFits(CGSize(width: availableWidth - 32, height: .greatestFiniteMagnitude)).height let imageHeight = (availableWidth - 32) * 9 / 16 actionButton.sizeToFit() let buttonHeight = actionButton.bounds.height let totalHeight = padding + titleHeight + itemSpacing + imageHeight + itemSpacing + buttonHeight + padding return CGSize(width: UIView.noIntrinsicMetric, height: totalHeight) } }
注意事项
- 不要在
init或者draw(_ rect:)方法中获取视图宽度,此时第一趟约束求解还未完成,拿到的宽度是不准确的 - 子视图内容动态变化时,需要调用
setNeedsLayout()(对应方案1)或者invalidateIntrinsicContentSize()(对应方案2)触发重新计算 - 两种方案不要同时混用,避免出现布局冲突
内容的提问来源于stack exchange,提问作者jspinks
相关产品推荐
相关产品推荐

