在将异步图像加载到UITableViewCell后滚动时,Swift图像会更改为错误的图像

我正在尝试在FriendsTableView(UITableView)单元格中异步加载图片。 图像加载正常,但是当我滚动表格时,图像会改变几次,错误的图像会被分配给错误的单元格。

我已经尝试了我在StackOverflow中可以找到的所有方法,包括向raw添加标签然后检查它但是没有用。 我还要validation应该使用indexPath更新的单元格并检查单元格是否存在。 所以我不知道为什么会这样。

这是我的代码:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("friendCell", forIndexPath: indexPath) as! FriendTableViewCell var avatar_url: NSURL let friend = sortedFriends[indexPath.row] //Style the cell image to be round cell.friendAvatar.layer.cornerRadius = 36 cell.friendAvatar.layer.masksToBounds = true //Load friend photo asyncronisly avatar_url = NSURL(string: String(friend["friend_photo_url"]))! if avatar_url != "" { getDataFromUrl(avatar_url) { (data, response, error) in dispatch_async(dispatch_get_main_queue()) { () -> Void in guard let data = data where error == nil else { return } let thisCell = tableView.cellForRowAtIndexPath(indexPath) if (thisCell) != nil { let updateCell = thisCell as! FriendTableViewCell updateCell.friendAvatar.image = UIImage(data: data) } } } } cell.friendNameLabel.text = friend["friend_name"].string cell.friendHealthPoints.text = String(friend["friend_health_points"]) return cell } 

这是因为UITableView重用了单元格。 以这种方式加载它们会导致异步请求在不同时间返回并弄乱订单。

我建议你使用一些图书馆,让你的生活更轻松如翠鸟。 它将为您下载和缓存图像。 您也不必担心异步调用。

https://github.com/onevcat/Kingfisher

你的代码看起来像这样:

 func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("friendCell", forIndexPath: indexPath) as! FriendTableViewCell var avatar_url: NSURL let friend = sortedFriends[indexPath.row] //Style the cell image to be round cell.friendAvatar.layer.cornerRadius = 36 cell.friendAvatar.layer.masksToBounds = true //Load friend photo asyncronisly avatar_url = NSURL(string: String(friend["friend_photo_url"]))! if avatar_url != "" { cell.friendAvatar.kf_setImageWithURL(avatar_url) } cell.friendNameLabel.text = friend["friend_name"].string cell.friendHealthPoints.text = String(friend["friend_health_points"]) return cell } 

在cellForRowAtIndexPath上:

1)为自定义单元格指定索引值。 例如,

 cell.tag = indexPath.row 

2)在主线程上,在分配图像之前,通过将图像与标记匹配来检查图像是否属于相应的单元格。

 dispatch_async(dispatch_get_main_queue(), ^{ if(cell.tag == indexPath.row) { UIImage *tmpImage = [[UIImage alloc] initWithData:imgData]; thumbnailImageView.image = tmpImage; }}); }); 

UPDATE

下面的代码确实有效,但我后来转而使用非常简单的KingFisher 。

结束更新

我不想使用像Alamofire这样的第三方库,而cell.tag也不适合我。 将初始图像设置为nil对我来说是个窍门。 我也缓存了图像。 这是我为UIImageView编写的扩展:

 let imageCache = NSCache() extension UIImageView { func downloadImage(from imgURL: String!) { let url = URLRequest(url: URL(string: imgURL)!) // set initial image to nil so it doesn't use the image from a reused cell image = nil // check if the image is already in the cache if let imageToCache = imageCache.object(forKey: imgURL! as NSString) { self.image = imageToCache return } // download the image asynchronously let task = URLSession.shared.dataTask(with: url) { (data, response, error) in if error != nil { print(error!) return } DispatchQueue.main.async { // create UIImage let imageToCache = UIImage(data: data!) // add image to cache imageCache.setObject(imageToCache!, forKey: imgURL! as NSString) self.image = imageToCache } } task.resume() } } 

您可以在cellForRowAt中调用此方法

 cell.postImage.downloadImage(from: self.posts[indexPath.row].pathToImage) 

Source1 Source2

这个问题的最佳解决方案是Swift 3或Swift 4简单地写下这两行

  cell.videoImage.image = nil cell.thumbnailimage.setImageWith(imageurl!) 

根据实施情况,可能会有很多因素导致这里的所有答案都不起作用(包括我的)。 检查标签对我来说不起作用,也没有检查缓存,我有一个自定义的Photo类,它带有完整的图像,缩略图和更多的数据,所以我也要照顾它,而不仅仅是防止图像被不正确地重复使用。 由于您可能会在下载完成后将图像分配给单元imageView,因此您需要取消下载并重置prepareForReuse()上需要的任何内容。

例如,如果你正在使用像SDWebImage这样的东西

  override func prepareForReuse() { super.prepareForReuse() self.imageView.sd_cancelCurrentImageLoad() self.imageView = nil //Stop or reset anything else that is needed here } 

如果您已经为imageview创建子类并自己处理下载,请确保在调用完成之前设置取消下载的方法,并在prepareForReuse()上调用cancel

例如

 imageView.cancelDownload() 

您也可以从UIViewController中取消它。 这本身或与一些答案结合将很可能解决这个问题。

斯威夫特3

  DispatchQueue.main.async(execute: {() -> Void in if cell.tag == indexPath.row { var tmpImage = UIImage(data: imgData) thumbnailImageView.image = tmpImage } })