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

UITableView中异步加载UIImage为NSTextAttachment的最优方案咨询

我之前也碰到过类似的痛点——在UITableViewCell里异步加载图片到NSTextAttachment,稍不注意就会出现复用错乱、加载卡顿、图片倒置这些问题。结合你的场景,给你梳理几个最优实现思路:

最优实现方案拆解

1. 用成熟图片加载库(优先推荐)

直接用Kingfisher或SDWebImage这类库是最高效的选择,它们自带异步加载、多级缓存、复用cell时的请求自动取消逻辑,完美适配UITableView的复用机制,不用自己造轮子。

举个Kingfisher的实现例子:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // 注意强转成你的自定义Cell类型,避免可选值处理麻烦
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTextCell
    
    // 因为你翻转了TableView,必须同步翻转TextView内容,否则图片/文字会倒置
    cell.textView.transform = CGAffineTransform(scaleX: 1, y: -1)
    
    let textAttachment = NSTextAttachment()
    guard let imageUrl = URL(string: "你的图片URL字符串") else {
        // URL无效时设置占位图
        textAttachment.image = UIImage(named: "placeholder")
        cell.textView.attributedText = NSAttributedString(attachment: textAttachment)
        return cell
    }

    // 异步加载图片并设置到Attachment
    KingfisherManager.shared.retrieveImage(with: imageUrl) { result in
        DispatchQueue.main.async {
            switch result {
            case .success(let value):
                textAttachment.image = value.image
                cell.textView.attributedText = NSAttributedString(attachment: textAttachment)
            case .failure(let error):
                print("图片加载失败:\(error.localizedDescription)")
                textAttachment.image = UIImage(named: "placeholder")
                cell.textView.attributedText = NSAttributedString(attachment: textAttachment)
            }
        }
    }

    // 关键:复用Cell时自动取消之前的加载请求,避免旧图片出现在新Cell里
    cell.textView.kf.cancelDownloadTask()
    
    return cell
}

2. 手动实现异步加载+缓存(不依赖第三方库)

如果不想引入第三方,就得自己处理异步请求、缓存、复用清理这三个核心点:
首先自定义Cell,在复用前取消未完成的请求:

class CustomTextCell: UITableViewCell {
    @IBOutlet weak var textView: UITextView!
    // 记录当前的加载任务,用于复用时取消
    var currentLoadTask: URLSessionDataTask?

    override func prepareForReuse() {
        super.prepareForReuse()
        currentLoadTask?.cancel()
        currentLoadTask = nil
        textView.attributedText = nil
    }
}

然后在cellForRowAt里实现加载逻辑:

// 全局或单例的内存缓存,自动在内存不足时清理
let imageCache = NSCache<NSString, UIImage>()

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTextCell
    cell.textView.transform = CGAffineTransform(scaleX: 1, y: -1)
    
    guard let imageUrl = URL(string: "你的图片URL字符串") else {
        let attachment = NSTextAttachment()
        attachment.image = UIImage(named: "placeholder")
        cell.textView.attributedText = NSAttributedString(attachment: attachment)
        return cell
    }

    // 先查内存缓存,命中直接显示
    if let cachedImage = imageCache.object(forKey: imageUrl.absoluteString as NSString) {
        let attachment = NSTextAttachment()
        attachment.image = cachedImage
        cell.textView.attributedText = NSAttributedString(attachment: attachment)
        return cell
    }

    // 未命中缓存,发起异步请求
    cell.currentLoadTask = URLSession.shared.dataTask(with: imageUrl) { data, _, error in
        guard let data = data, let loadedImage = UIImage(data: data), error == nil else {
            DispatchQueue.main.async {
                let attachment = NSTextAttachment()
                attachment.image = UIImage(named: "placeholder")
                cell.textView.attributedText = NSAttributedString(attachment: attachment)
            }
            return
        }

        // 存入内存缓存
        imageCache.setObject(loadedImage, forKey: imageUrl.absoluteString as NSString)

        DispatchQueue.main.async {
            // 关键:检查Cell是否还对应原IndexPath,避免复用错乱
            guard tableView.indexPath(for: cell) == indexPath else { return }
            
            let attachment = NSTextAttachment()
            attachment.image = loadedImage
            cell.textView.attributedText = NSAttributedString(attachment: attachment)
        }
    }

    cell.currentLoadTask?.resume()
    return cell
}

3. 额外优化细节

  • 图片尺寸适配:加载完成后可以调整图片大小,让它适配TextView宽度,避免超出屏幕:
    // 给UIImage加个调整尺寸的扩展
    extension UIImage {
        func resize(to targetSize: CGSize) -> UIImage? {
            let format = UIGraphicsImageRendererFormat()
            format.scale = UIScreen.main.scale
            let renderer = UIGraphicsImageRenderer(size: targetSize, format: format)
            return renderer.image { _ in
                self.draw(in: CGRect(origin: .zero, size: targetSize))
            }
        }
    }
    // 使用时:
    let maxWidth = cell.textView.bounds.width - 16 // 留左右边距
    let resizedImage = loadedImage.resize(to: CGSize(width: maxWidth, height: maxWidth * loadedImage.size.height / loadedImage.size.width))
    
  • 避免重复请求:如果用手动实现,建议加个字典记录正在进行的请求,同一个URL不要重复发起请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 11:55:29