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

如何检测UITextView完成内容重排以更新模糊视图?

解决UITextView内容重排完成后的模糊视图更新问题

我之前实现了一个BlurringView自定义UIView子类,通过blur布尔属性控制是否显示子视图的模糊版本,大部分场景下运行正常,但当子视图为UITextView时,设备旋转会出现问题:UITextView的bounds先随视图尺寸变化,但内容重排要等到旋转动画完成后才会执行。现有的尺寸监听方式(bounds的didSet、traitCollectionDidChange()、设备旋转通知)都会在内容重排前触发,直接更新模糊视图无法得到正确的效果,目前只能通过延迟0.33秒的“魔法数字”方案临时解决,代码如下:

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    print("In \(#function))")
    
    // 仅当添加0.33秒延迟时,才能在TextView布局完成后更新模糊图
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.33) {
        print("Updating blur image. Bounds = \(self.bounds)")
        self.updateBlurImage()
    }
}

以下是几种更优雅的替代方案,无需依赖固定延迟:

方案1:利用CATransaction监听旋转动画完成

设备旋转的尺寸变化属于系统动画,被包裹在CATransaction中。我们可以通过设置CATransaction的完成回调,在旋转动画结束后再更新模糊视图,此时UITextView的内容重排已完成:

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    
    CATransaction.begin()
    CATransaction.setCompletionBlock { [weak self] in
        guard let self = self else { return }
        self.updateBlurImage()
    }
    CATransaction.commit()
}

该方案无需针对UITextView做特殊判断,同时能覆盖所有系统驱动的视图尺寸变化场景。

方案2:KVO监听UITextView的contentSize变化

UITextView完成内容重排后,其contentSize会发生改变(尤其是宽度变化导致文本换行、高度调整时)。我们可以在BlurringView中对其子视图做类型判断,若为UITextView则添加KVO监听:

// 在BlurringView添加子视图时调用
func setupTextViewObserver() {
    guard let textView = subview as? UITextView else { return }
    textView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    guard keyPath == "contentSize", object is UITextView else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        return
    }
    // 异步执行确保布局完全稳定
    DispatchQueue.main.async { [weak self] in
        self?.updateBlurImage()
    }
}

// 记得在deinit中移除监听
deinit {
    if let textView = subview as? UITextView {
        textView.removeObserver(self, forKeyPath: "contentSize")
    }
}

这种方式能精准捕捉UITextView的内容重排完成时机,即使是旋转以外的尺寸变化场景(比如父视图手动调整大小)也能生效。

方案3:自定义UITextView子类监听布局完成

创建UITextView的子类,在其layoutSubviews方法完成后触发回调,让BlurringView在回调中更新模糊视图:

class LayoutAwareTextView: UITextView {
    var onLayoutCompleted: (() -> Void)?
    
    override func layoutSubviews() {
        super.layoutSubviews()
        // 布局完成后通知外部
        onLayoutCompleted?()
    }
}

在BlurringView中使用该子类并绑定回调:

// 假设subview是LayoutAwareTextView实例
if let textView = subview as? LayoutAwareTextView {
    textView.onLayoutCompleted = { [weak self] in
        self?.updateBlurImage()
    }
}

该方案逻辑直观,适合需要对UITextView布局做更多自定义处理的场景。

内容的提问来源于stack exchange,提问作者Duncan C

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 19:04:52