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

时间:2015-08-21 05:26:29

标签: ios swift uitableview uiimageview

我有一个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

}

6 个答案:

答案 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)

Swift 3

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

ImageCacheLoader.swift

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)

雨燕5

因此,解决问题的一种简单方法是创建一个自定义类,该类将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")