tableView单元格有时显示错误的图像?

我有一个tableView,在单元格中显示一个图像。 大多数情况下,将显示正确的图像,但偶尔会显示错误的图像(通常是非常快速地向下滚动tableView)。 我异步下载图像并将它们存储在缓存中。 找不到还有什么可能导致这个问题? 以下是代码:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { // try to reuse cell let cell:CustomCell = tableView.dequeueReusableCellWithIdentifier("DealCell") as CustomCell // get the venue image let currentVenueImage = deals[indexPath.row].venueImageID let unwrappedVenueImage = currentVenueImage var venueImage = self.venueImageCache[unwrappedVenueImage] let venueImageUrl = NSURL(string: "http://notrealsite.com/restaurants/\(unwrappedVenueImage)/photo") // reset reused cell image to placeholder cell.venueImage.image = UIImage(named: "placeholder venue") // async image if venueImage == nil { let request: NSURLRequest = NSURLRequest(URL: venueImageUrl!) NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in if error == nil { venueImage = UIImage(data: data) self.venueImageCache[unwrappedVenueImage] = venueImage dispatch_async(dispatch_get_main_queue(), { // fade image in cell.venueImage.alpha = 0 cell.venueImage.image = venueImage cell.venueImage.fadeIn() }) } else { } }) } else{ cell.venueImage.image = venueImage } return cell } 

我认为问题在于sendAsynchronousRequest 。 如果滚动的速度超过此速度,则在重用单元格时,最终可能会使用旧的completionHandler替换“错误”单元格(因为它现在显示的是不同的条目)。 您需要在完成处理程序中检查它仍然是您要显示的图像。

所以在之前的一些答案指向了正确的方向后,这就是我添加的代码,这似乎已经完成了。 图像全部加载并显示为现在应该显示的图像。

 dispatch_async(dispatch_get_main_queue(), { // check if the cell is still on screen, and only if it is, update the image. let updateCell = tableView .cellForRowAtIndexPath(indexPath) if updateCell != nil { // fade image in cell.venueImage.alpha = 0 cell.venueImage.image = venueImage cell.venueImage.fadeIn() } }) 

这是出列的可重复使用单元的问题。 在图像下载完成方法中,您应该检查此下载的图像是否是正确的索引路径。 您需要存储存储索引路径和相应URL的映射数据结构。 下载完成后,您需要检查此URL是否属于当前索引路径,否则加载该下载索引路径的单元格并设置图像。

斯威夫特3

我使用NSCache为图像加载器编写了自己的light实现。 没有细胞图像闪烁!

ImageCacheLoader.swift

 typealias ImageCacheLoaderCompletionHandler = ((UIImage) -> ()) class ImageCacheLoader { var task: URLSessionDownloadTask! var session: URLSession! var cache: NSCache! init() { session = URLSession.shared task = URLSessionDownloadTask() self.cache = NSCache() } func obtainImageWithPath(imagePath: String, completionHandler: @escaping ImageCacheLoaderCompletionHandler) { if let image = self.cache.object(forKey: imagePath as NSString) { DispatchQueue.main.async { completionHandler(image) } } else { /* You need placeholder image in your assets, if you want to display a placeholder to user */ let placeholder = #imageLiteral(resourceName: "placeholder") DispatchQueue.main.async { completionHandler(placeholder) } let url: URL! = URL(string: imagePath) task = session.downloadTask(with: url, completionHandler: { (location, response, error) in if let data = try? Data(contentsOf: url) { let img: UIImage! = UIImage(data: data) self.cache.setObject(img, forKey: imagePath as NSString) DispatchQueue.main.async { completionHandler(img) } } }) task.resume() } } } 

用法示例

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Identifier") cell.title = "Cool title" imageLoader.obtainImageWithPath(imagePath: viewModel.image) { (image) in // Before assigning the image, check whether the current cell is visible for ensuring that it's right cell if let updateCell = tableView.cellForRow(at: indexPath) { updateCell.imageView.image = image } } return cell } 

以下代码更改对我有用。

您可以提前下载图像并将其保存在用户无法访问的应用程序目录中。 您可以从tableview中的应用程序目录中获取这些图像。

 // Getting images in advance var paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String var dirPath = paths.stringByAppendingPathComponent("XYZ/") var imagePath = paths.stringByAppendingPathComponent("XYZ/\(ImageName)" ) println(imagePath) var checkImage = NSFileManager.defaultManager() if checkImage.fileExistsAtPath(imagePath) { println("Image already exists in application Local") } else { println("Getting Image from Remote") checkImage.createDirectoryAtPath(dirPath, withIntermediateDirectories: true, attributes: nil, error: nil) let request: NSURLRequest = NSURLRequest(URL: NSURL(string: urldata as! String)!) let mainQueue = NSOperationQueue.mainQueue() NSURLConnection.sendAsynchronousRequest(request, queue: mainQueue, completionHandler: { (response, data, error) -> Void in if let httpResponse = response as? NSHTTPURLResponse { // println("Status Code for successful------------------------------------>\(httpResponse.statusCode)") if (httpResponse.statusCode == 502) { //Image not found in the URL, I am adding default image self.logoimg.image = UIImage(named: "default.png") println("Image not found in the URL") } else if (httpResponse.statusCode == 404) { //Image not found in the URL, I am adding default image self.logoimg.image = UIImage(named: "default.png") println("Image not found in the URL") } else if (httpResponse.statusCode != 404) { // Convert the downloaded data in to a UIImage object let image = UIImage(data: data) // Store the image in to our cache UIImagePNGRepresentation(UIImage(data: data)).writeToFile(imagePath, atomically: true) dispatch_async(dispatch_get_main_queue(), { self.logoimg.contentMode = UIViewContentMode.ScaleAspectFit self.logoimg.image = UIImage(data: data) println("Image added successfully") }) } } }) } // Code in cellForRowAtIndexPath var checkImage = NSFileManager.defaultManager() if checkImage.fileExistsAtPath(imagePath) { println("Getting Image from application directory") let getImage = UIImage(contentsOfFile: imagePath) imageView.backgroundColor = UIColor.whiteColor() imageView.image = nil imageView.image = getImage imageView.frame = CGRectMake(xOffset, CGFloat(4), CGFloat(30), CGFloat(30)) cell.contentView.addSubview(imageView) } else { println("Default image") imageView.backgroundColor = UIColor.whiteColor() imageView.image = UIImage(named: "default.png")! imageView.frame = CGRectMake(xOffset, CGFloat(4), CGFloat(30), CGFloat(30)) cell.contentView.addSubview(imageView) }