自定义UIView子类draw(_ rect: CGRect)方法接收到错误的旧矩形,尽管intrinsicContentSize返回正确尺寸
我之前在做自定义TextKit驱动的标签控件时,完完全全碰到过这个问题!当时也纳闷为什么intrinsicContentSize明明返回对了,draw方法里的rect却还是旧的,折腾了好一阵才找到根源。其实核心问题大多出在TextKit组件没有和View的当前状态绑定,或者系统没有在尺寸变化后触发完整的布局+重绘流程,下面给你几个针对性的解决办法:
让TextKit的文本容器尺寸和View实时同步
你的setupTextKit方法里,一定要确保textContainer的尺寸和View的可用宽度/intrinsic尺寸匹配,不能用固定值或者旧的尺寸。比如修改成这样:private func setupTextKit() { // 先确保textStorage、textContainer这些组件已经正确初始化绑定 textStorage.setAttributedString(attributedText ?? NSAttributedString()) layoutManager.addTextContainer(textContainer) // 用当前的有效宽度设置文本容器 let targetWidth = preferredMaxLayoutWidth ?? bounds.width textContainer.size = CGSize(width: targetWidth, height: .greatestFiniteMagnitude) // 重新计算文本实际高度,同步到intrinsicContentSize let actualTextHeight = layoutManager.usedRect(for: textContainer).height self.intrinsicContentSize = CGSize(width: targetWidth, height: actualTextHeight) }不要依赖draw方法传入的rect,用TextKit计算的实际绘制区域
系统有时候会在布局过渡过程中传入旧的rect(比如动画时的中间状态),正确的做法是直接用TextKit算出的文本区域来绘制,完全不依赖参数里的rect:override func draw(_ rect: CGRect) { super.draw(rect) guard let textContainer = layoutManager.textContainers.first else { return } // 获取TextKit计算出的文本实际占用区域 let textActualRect = layoutManager.usedRect(for: textContainer) // 可以根据需求调整绘制位置,比如居中显示 let drawingOrigin = CGPoint( x: (bounds.width - textActualRect.width) / 2, y: (bounds.height - textActualRect.height) / 2 ) // 按照TextKit的布局绘制字形 layoutManager.drawGlyphs( forGlyphRange: NSRange(location: 0, length: layoutManager.numberOfGlyphs), at: drawingOrigin ) }补全状态变化后的通知流程
在你的attributedText的didSet里,现在只有invalidateIntrinsicContentSize和setNeedsDisplay,还需要加上setNeedsLayout来触发系统重新计算View的bounds,这样后续的重绘才能拿到正确的尺寸:var attributedText : NSAttributedString? { didSet{ setupTextKit() preferredMaxLayoutWidth = nil invalidateIntrinsicContentSize() setNeedsLayout() // 新增:让系统重新计算布局,更新bounds setNeedsDisplay() } }重写layoutSubviews,处理布局变化时的同步
当View的bounds因为父布局调整而变化时,要重新同步TextKit的配置,确保文本布局始终正确:override func layoutSubviews() { super.layoutSubviews() // 如果没有手动设置preferredMaxLayoutWidth,就用当前View的宽度 if preferredMaxLayoutWidth == nil, bounds.width > 0 { preferredMaxLayoutWidth = bounds.width setupTextKit() invalidateIntrinsicContentSize() setNeedsDisplay() } }
这些调整后,你的自定义Label应该就能正确响应尺寸变化,draw方法里也能基于当前正确的状态来绘制文本了。
内容来源于stack exchange

