我有一个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
}
答案 0 :(得分:4)
我认为问题出在sendAsynchronousRequest
上。如果您滚动速度超过此速度,则在重复使用单元格时,最终可能会使用旧的completionHandler
替换“错误”单元格(因为它现在显示的是不同的条目)。您需要在完成处理程序中检查它仍然是您要显示的图像。
答案 1 :(得分:3)
所以在之前的一些答案指向了正确的方向之后,这就是我添加的代码,这似乎已经完成了。图像全部加载并按现在显示。
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()
}
})
答案 2 :(得分:2)
这是出列的可重复使用单元格的问题。在图像下载完成方法中,您应该检查此下载的图像是否是正确的索引路径。您需要存储存储索引路径和相应URL的映射数据结构。下载完成后,您需要检查此URL是否属于当前索引路径,否则加载该下载索引路径的单元格并设置图像。
答案 3 :(得分:2)
我使用NSCache为图像加载器编写自己的light实现。 无细胞图像闪烁!
typealias ImageCacheLoaderCompletionHandler = ((UIImage) -> ())
class ImageCacheLoader {
var task: URLSessionDownloadTask!
var session: URLSession!
var cache: NSCache<NSString, UIImage>!
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
}
答案 4 :(得分:1)
以下代码更改对我有用。
您可以提前下载图像并将其保存在用户无法访问的应用程序目录中。您可以从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)
}
答案 5 :(得分:0)
因此,解决问题的一种简单方法是创建一个自定义类,该类将UIImageView子类化。
添加一个属性以存储url字符串。 最初将图像设置为占位符以停止闪烁。 从响应数据解析和设置图像时,将类属性url字符串与传递给函数的url字符串进行比较,并确保它们相等。 使用键作为url字符串缓存图像并进行相应的检索。
注意:请勿扩展UIImageview,因为我们计划添加属性 imageUrl 。
参考:https://www.youtube.com/watch?v=XFvs6eraBXM
let imageCache = NSCache<NSString, UIImage>()
class CustomIV: UIImageView {
var imageUrl: String?
func loadImage(urlStr: String) {
imageUrl = urlStr
image = UIImage(named: "placeholder venue")
if let img = imageCache.object(forKey: NSString(string: imageUrl!)) {
image = img
return
}
guard let url = URL(string: urlStr) else {return}
imageUrl = urlStr
URLSession.shared.dataTask(with: url) { data, response, error in
if let err = error {
print(err)
} else {
DispatchQueue.main.async {
let tempImg = UIImage(data: data!)
if self.imageUrl == urlStr {
self.image = tempImg
}
imageCache.setObject(tempImg!, forKey: NSString(string: urlStr))
}
}
}.resume()
}
}
只需将表视图单元格的图像视图更新为CustomIV对象。 然后使用以下命令更新图像:
cell.venueImage.loadImage(urlStr: "http://notrealsite.com/restaurants/\(unwrappedVenueImage)/photo")