Swift 3中如何将远程加载的图片添加到UITableView并显示?
解决UITableView远程图片显示问题
首先,你的核心问题有几个:下载完图片后没有关联到表格单元格、没有在主线程更新UI、数据结构不利于管理图片,还有没处理单元格复用的问题。我一步步帮你解决:
第一步:重构数据模型(替换拼接字符串的方式)
你现在把节目名称和类型拼成字符串存进TableData,没办法关联对应的图片。我们应该创建一个专门的模型来存储每个节目的所有信息:
struct Show { let name: String let genre: String let thumbnailURL: String var image: UIImage? // 用来缓存下载好的图片 }
然后把原来的TableData替换成存储模型的数组:
var shows: [Show] = []
解析JSON的时候,初始化Show对象并添加到数组(建议用Swift原生的字典数组,不要用NSArray/NSDictionary,更安全):
if let showsList = json as? [[String: Any]] { for showDict in showsList { guard let name = showDict["show"] as? String, let genre = showDict["genre"] as? String, let thumbnailURL = showDict["thumbnail"] as? String else { continue // 解析失败就跳过当前条目 } let show = Show(name: name, genre: genre, thumbnailURL: thumbnailURL, image: nil) shows.append(show) } tableView.reloadData() // 解析完成后刷新表格 }
第二步:异步下载图片并关联到单元格
下载图片是异步操作,必须回到主线程更新UI,还要处理单元格复用的问题(避免滑动时图片错乱)。这里分两种方案:
方案1:手动实现(适合学习原理)
我们自定义一个单元格,重写prepareForReuse方法来取消未完成的下载任务,防止图片错乱:
class ShowTableViewCell: UITableViewCell { var downloadTask: URLSessionDataTask? override func prepareForReuse() { super.prepareForReuse() // 复用单元格时取消之前的下载任务 downloadTask?.cancel() downloadTask = nil // 清空旧内容 imageView?.image = nil textLabel?.text = nil } }
然后在cellForRowAt方法里处理图片加载:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: "ShowCell", for: indexPath) as? ShowTableViewCell else { return UITableViewCell() } let show = shows[indexPath.row] // 设置文本内容 cell.textLabel?.text = "\(show.name) | \(show.genre)" // 设置占位图(可选,提升用户体验) cell.imageView?.image = UIImage(named: "placeholder") // 如果已经下载过图片,直接显示 if let cachedImage = show.image { cell.imageView?.image = cachedImage return cell } // 没有缓存,开始下载图片 guard let imageURL = URL(string: show.thumbnailURL) else { return cell } let task = URLSession.shared.dataTask(with: imageURL) { [weak self, weak cell] data, response, error in // 异步任务完成后,必须回到主线程更新UI! DispatchQueue.main.async { guard let self = self, let cell = cell else { return } if let error = error { print("图片下载失败:\(error.localizedDescription)") return } guard let imageData = data, let downloadedImage = UIImage(data: imageData) else { print("无法解析图片数据") return } // 缓存图片到模型里 self.shows[indexPath.row].image = downloadedImage // 给当前单元格设置图片 cell.imageView?.image = downloadedImage } } // 把任务赋值给单元格,方便复用的时候取消 cell.downloadTask = task task.resume() return cell }
方案2:用第三方库(推荐,适合新手)
手动实现需要处理很多细节(缓存、复用、错误处理),新手可以直接用成熟的图片加载库,比如Kingfisher:
- 先通过CocoaPods或Swift Package Manager安装Kingfisher
- 在代码中导入库,然后在
cellForRowAt里一行代码搞定:
import Kingfisher func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "ShowCell", for: indexPath) let show = shows[indexPath.row] cell.textLabel?.text = "\(show.name) | \(show.genre)" // Kingfisher自动处理下载、缓存、占位图和复用问题 if let imageURL = URL(string: show.thumbnailURL) { cell.imageView?.kf.setImage(with: imageURL, placeholder: UIImage(named: "placeholder")) } return cell }
你原来代码的核心问题总结
- 没有关联图片到单元格:你下载完图片只创建了
UIImage,但没把它设置到cell的imageView里 - 没有在主线程更新UI:异步任务的回调在后台线程,UI操作必须在主线程执行,否则不会生效
- 数据结构不合理:用拼接字符串无法对应到每个节目的图片,必须用模型管理
- 没处理单元格复用:滑动表格时旧的下载任务可能还在运行,导致图片显示错乱
内容的提问来源于stack exchange,提问作者darkginger
相关产品推荐
相关产品推荐

