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
相关产品推荐
相关产品推荐

