【问题标题】:tableView cell is displaying the wrong image sometimes?tableView 单元格有时会显示错误的图像?
【发布时间】:2015-11-14 23:05:10
【问题描述】:

我有一个在单元格中显示图像的 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

}

【问题讨论】:

  • 为什么每次都重置重用的单元格图像?仅当未找到 venueImage 时才需要重置。
  • 让 unwrappedVenueImage = currentVenueImage 不解开 currentVenueImage。展开是“如果让...”
  • 使用 AsyncImageView 而不是 UIImageView 它会自动提供缓存,你可以通过设置 setImageWithURL 来使用它,通过你的 URL,它也有微调器,所以不需要任何占位符图像。
  • 您使用的是哪个 swift 版本?

标签: ios swift uitableview uiimageview


【解决方案1】:

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

【讨论】:

  • 谢谢,我认为你的权利。知道我会如何编码吗? Rishi 的链接在 obj-c 中有解决方案,但我不知道如何在 swift 中做同样的事情。
【解决方案2】:

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

【讨论】:

  • 嗨 Tushar,这听起来正是我需要做的。我不太确定如何编写代码。如果您能提供帮助,我将不胜感激,干杯。
  • @tushar 你应该添加一个例子
【解决方案3】:

以下代码更改对我有用。

您可以提前下载图片并将其保存在用户无法访问的应用程序目录中。您可以从 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)
}

【讨论】:

    【解决方案4】:

    因此,在前面的一些答案为我指明了正确的方向之后,这就是我添加的代码,这似乎起到了作用。图像全部加载并按现在应有的方式显示。

    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()
                        }
                    })
    

    【讨论】:

    • 你在哪里添加了这段代码?在 cellForRowAt 内?还是在tableViewCell中?
    • 我有同样的问题,如果消息的类型是文档类型并且如果它是图像,我会在其中放置 UIImage(named:"") 我从 firebase 下载它,但是当图像下载它时替换文档消息的占位符图像。
    【解决方案5】:

    斯威夫特 3

    我使用 NSCache 为图像加载器编写了自己的轻量级实现。 没有单元格图像闪烁!

    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
    }
    

    【讨论】:

      【解决方案6】:

      斯威夫特 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()
          }
      }
      

      只需将 tableview 单元格的 imageview 更新为 CustomIV 对象。 然后使用以下命令更新图像:

      cell.venueImage.loadImage(urlStr: "http://notrealsite.com/restaurants/\(unwrappedVenueImage)/photo")
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-11-29
        • 2015-05-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-03-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多