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

UICollectionView因NSAttributedString图片导致滚动卡顿问题求助

嘿,这个滚动卡顿的问题我之前也碰到过,大概率是带图片的富文本渲染和Cell复用没做好优化导致的。给你几个实用的解决思路,亲测有效:

解决方案

1. 预加载并缓存默认的带图富文本

每次Cell复用都重新创建带图片的NSAttributedString是很大的性能消耗——毕竟图片附件的初始化、排版计算都是耗时操作。建议提前在全局或ViewModel里预创建好这个默认富文本并缓存:

// 全局缓存的默认富文本,只初始化一次
let emptyRecordAttributedString: NSAttributedString = {
    let attachment = NSTextAttachment()
    // 提前加载并缩放好适配TextView的图片,避免临时缩放开销
    let originalImage = UIImage(named: "empty-record-icon")!
    let targetSize = CGSize(width: 40, height: 40)
    let scaledImage = UIGraphicsImageRenderer(size: targetSize).image { _ in
        originalImage.draw(in: CGRect(origin: .zero, size: targetSize))
    }
    attachment.image = scaledImage
    attachment.bounds = CGRect(origin: .zero, size: targetSize)
    
    let mutableAttrStr = NSMutableAttributedString(attachment: attachment)
    mutableAttrStr.append(NSAttributedString(string: "\n今天还没有记录哦~"))
    
    // 统一设置字体样式,避免TextView重复计算排版
    let fullRange = NSRange(location: 0, length: mutableAttrStr.length)
    mutableAttrStr.addAttributes([
        .font: UIFont.systemFont(ofSize: 16),
        .foregroundColor: UIColor.secondaryLabel
    ], range: fullRange)
    
    return mutableAttrStr
}()

之后Cell需要显示默认内容时,直接复用这个预创建的对象就行,不用每次都重复造轮子。

2. 优化UICollectionViewCell的复用逻辑

Cell复用是性能优化的核心,一定要避免在cellForItemAt里做耗时操作:

  • 在prepareForReuse()里轻量重置状态,避免旧内容干扰:
override func prepareForReuse() {
    super.prepareForReuse()
    // 清空TextView内容,避免滚动时出现内容闪烁
    self.textView.attributedText = nil
    self.textView.text = nil
}
  • 提前加载Core Data数据到内存数组,不要在cellForItemAt里同步查询数据库:
// 在ViewModel或ViewController里提前异步加载所有记录
var dailyRecords: [DailyRecord] = []

func loadAllRecords() {
    let fetchRequest: NSFetchRequest<DailyRecord> = DailyRecord.fetchRequest()
    fetchRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)]
    
    // 后台线程执行Core Data查询,不阻塞主线程
    persistentContainer.viewContext.perform {
        do {
            let records = try fetchRequest.execute()
            DispatchQueue.main.async {
                self.dailyRecords = records
                self.collectionView.reloadData()
            }
        } catch {
            print("加载记录失败:\(error.localizedDescription)")
        }
    }
}

// 在cellForItemAt里直接从内存数组取数据
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DailyCell", for: indexPath) as! DailyCell
    let record = dailyRecords[indexPath.item]
    
    if let content = record.content, !content.isEmpty {
        cell.textView.text = content
    } else {
        // 直接用预缓存的富文本
        cell.textView.attributedText = emptyRecordAttributedString
    }
    
    return cell
}

3. 降低UITextView的渲染负担

UITextView默认的一些配置会额外增加渲染开销,针对这种只读的默认状态,可以做这些优化:

// 在Cell初始化TextView时配置
func setupTextView() {
    textView.isEditable = false
    textView.isScrollEnabled = false
    textView.backgroundColor = .clear
    // 关闭不必要的排版内边距,减少渲染区域
    textView.textContainerInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
    textView.textContainer.lineFragmentPadding = 0
    // 开启光栅化,把TextView内容缓存为位图,减少滚动时的重复渲染
    textView.layer.shouldRasterize = true
    textView.layer.rasterizationScale = UIScreen.main.scale
}

开启光栅化后,TextView的内容会被缓存成一张图片,滚动时直接复用这张图片,不用重新计算富文本排版。

4. 检查图片资源的优化

如果你的默认图片尺寸过大,即使缩放后也会占用更多内存和渲染时间。建议:

  • 使用适配TextView显示尺寸的图片(比如40x40的图标就不要用200x200的原图)
  • 把图片转成更高效的格式或者压缩到合适的大小,减少内存占用

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 08:47:36