自定义UIView使用IntrinsicContentSize出现内容拉伸及IB高度异常问题
问题1解答
- 为什么IB里高度没有更新为300:
Interface Builder的设计时渲染流程是单轮闭环的,首次计算自定义视图的固有尺寸时,height属性还保留初始值50,后续你在draw(_:)方法中修改height并调用invalidateIntrinsicContentSize(),不会触发IB侧的二次布局重算流程,因此IB中始终以初始的50作为视图高度。 - 未调用
super.draw(_:)背景色仍正常渲染是预期行为:
UIView的背景色渲染是系统在调用draw(_:)方法之前独立完成的逻辑,和你是否调用父类的draw方法无关,draw(_:)仅负责处理自定义的绘制内容,不会影响系统层面的基础视图属性渲染。
问题2解答
出现灰色矩形被拉伸到150高度的核心原因是UIView默认的contentMode属性为.scaleToFill:
- 首次触发
draw(_:)时,视图的高度为初始的50,你绘制了高度为25的灰色矩形(占视图高度的1/2),系统会将本次绘制的内容缓存为位图 - 你在
draw中修改height为300并触发固有尺寸更新后,视图的bounds高度变为300,默认的.scaleToFill模式会直接将之前缓存的位图拉伸适配新的视图尺寸,因此原本25高的灰色矩形会等比拉伸为150高,正好是300的1/2。
正确实现方案
要实现高度自适应的自定义视图,需要调整实现逻辑,不要在draw(_:)方法中修改视图布局相关属性:
@IBDesignable class MyIntrinsicView: UIView { // 触发高度更新的业务属性,可根据实际需求替换为文本、内边距等 @IBInspectable var contentHeight: CGFloat = 50 { didSet { invalidateIntrinsicContentSize() // 内容变化需要重绘时主动调用 setNeedsDisplay() } } override init(frame: CGRect) { super.init(frame: frame) commonInit() } required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } private func commonInit() { // 尺寸变化时自动重绘,避免缓存拉伸 contentMode = .redraw } override var intrinsicContentSize: CGSize { // 宽度不做固有约束,跟随父视图/横向约束 return CGSize(width: UIView.noIntrinsicMetric, height: contentHeight) } override func draw(_ rect: CGRect) { super.draw(rect) guard let context = UIGraphicsGetCurrentContext() else { return } context.setFillColor(UIColor.gray.cgColor) // 这里始终绘制顶部25高的灰色条,不会被拉伸 context.fill(CGRect(x: 0, y: 0, width: bounds.width, height: 25)) } override func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() invalidateIntrinsicContentSize() } }
如果需要动态修改高度,只需要修改contentHeight属性即可自动触发尺寸更新和重绘,IB和运行时表现会保持一致。
内容的提问来源于stack exchange,提问作者Andrei Herford
相关产品推荐
相关产品推荐

