You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义UIView子类draw(_ rect: CGRect)方法接收到错误的旧矩形,尽管intrinsicContentSize返回正确尺寸

自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 13:10:29