在使用YYLabel进行布局约束的时候,会出现创建新的View上,高度正确,但是YYLabel的高度不对的问题。
之前调试了很久都没有很好的解决,现在用Cursor分析解决了。这里记录下。
原理
这行代码之所以能修复 bug,要理解 YYLabel 的 intrinsicContentSize 工作机制以及 Auto Layout 的 layout 生命周期顺序。
Auto Layout layout 生命周期顺序
setNeedsUpdateConstraints()
↓
updateConstraints() ← 我们在这里
↓
Auto Layout 求解约束 → 调用每个 view 的 intrinsicContentSize
↓
layoutSubviews() ← 之前只在这里设置 preferredMaxLayoutWidth(太晚了)
YYLabel.intrinsicContentSize 的行为
YYLabel 的 intrinsicContentSize 内部逻辑大致是:
override var intrinsicContentSize: CGSize {
if preferredMaxLayoutWidth > 0 {
// ✅ 同步调用 sizeThatFits — 立即计算多行高度,不依赖 textLayout
return sizeThatFits(CGSize(width: preferredMaxLayoutWidth, height: .greatestFiniteMagnitude))
} else {
// ❌ 依赖异步生成的 textLayout,第一次为 nil → 返回错误高度
return textLayout?.textBoundingSize ?? super.intrinsicContentSize
}
}
Bug 的根因
preferredMaxLayoutWidth 原来只在 layoutSubviews 里设置:
updateConstraints 运行
→ Auto Layout 询问 titleLabel.intrinsicContentSize
→ preferredMaxLayoutWidth == 0(还没到 layoutSubviews)
→ 走 else 分支 → textLayout 是 nil(YYLabel 异步渲染还没完成)
→ 返回错误高度(0 或 27 的最小值)
→ topView 高度 = 13 + 27 + 3 + 0 + 13 = 56 < 72 → 钳位到 72
layoutSubviews 才设置 preferredMaxLayoutWidth ← 已经太晚了
Fix 的原理
把 preferredMaxLayoutWidth 提前到 updateConstraints 里设置,让它先于 Auto Layout 询问 intrinsicContentSize 就位:
updateConstraints 运行
→ preferredMaxLayoutWidth = contentView.bounds.width - 40 - 112 = 278 ← 提前设置
→ super.updateConstraints() 触发 Auto Layout 重新求解
→ Auto Layout 询问 titleLabel.intrinsicContentSize
→ preferredMaxLayoutWidth == 278(已就位)✅
→ 同步调用 sizeThatFits(width: 278) → 立即算出 3 行的真实高度 61pt
→ topView 高度 = 13 + 61 + 3 + 31 + 13 = 121 ✅
contentView.bounds.width - 40 - 112 中:
-
40= bgView 左右各 20pt 的内缩 -
112= leftIcon(40) + offset(12) + rightButton(40) + offset(20) 等水平占用
contentView.bounds.width 在 updateConstraints 时已经由 table view 赋予了正确宽度(430pt),所以计算有效。