快速滚动时在 UITableView 单元格中异步加载了错误的图片

Wrong Picture asynchronously loaded in UITableView-cell when scrolling fast

我有一个包含用户列表及其个人资料图片的 UITable-View。 我正在为每个播放器异步加载图片 (http://api/pictures/{userid}):

func loadImageAsync(imageUrl: URL, completionHandler handler: @escaping (_ success: Bool, _ image: UIImage?) -> Void){

DispatchQueue.global(qos: .userInitiated).async { () -> Void in

    if let imgData = try? Data(contentsOf: imageUrl), let img = UIImage(data: imgData) {
        DispatchQueue.main.async(execute: { () -> Void in
            handler(true, img)
        })
    } else {
        handler(false, nil)
    }
}

在 cellForRowAt-Index-Fuction 的完成处理程序中,我正在设置图片。

       loadImageAsync(imageUrl: imageUrl!, label: ip) { (success, image, backlabel) -> Void in
            if(success){
                cell.profilePictureView.image = image
            }
        }

但是,当我快速滚动时,一些图片加载到错误的单元格中。 为了防止重用问题,我是 "resetting" 每次重用后的图像视图:

    override func prepareForReuse() {
        profilePictureView.image  = UIImage(named: "defaultProfilePicture")
    }

但是为什么在快速滚动时仍然有一些图片加载错误?

嗯,我也是这么想的

__Update: 因此,我用一个标签参数(类型 Any)扩展了该函数,该参数在放入函数时返回。我试图将参数(用于索引路径)与当前索引路径进行比较。实际上,这应该可行 - 不是吗?!

            loadImageAsync(imageUrl: imageUrl!, label: ip) { (success, image, backlabel) -> Void in

            cell.loader.stopAnimating()

            if (backlabel as! IndexPath == indexPath) {

                //set image...

但是,它并没有显示出任何效果。你知道为什么或有任何其他解决方案来解决这个问题吗?

问题是,如果您快速滚动,下载可能会花费足够长的时间,以至于在下载完成时,相关单元格已滚出屏幕并被回收用于数据模型中的不同 indexPath。

诀窍是在完成块中向 table 视图询问该 indexPath 处的单元格,并且仅在获得单元格返回时才安装图像:

   loadImageAsync(imageUrl: imageUrl!, label: ip, for indexPath: IndexPath) { (success, image, backlabel) -> Void in
        if(success){
            let targetCell = tableview.cell(for: indexPath)
            targetCell.profilePictureView.image = image
        }
    }

编辑:

像这样重新定义您的 loadImageAsync 函数:

func loadImageAsync(imageUrl: URL,
  indexPath: IndexPath, 
  completionHandler handler: @escaping (_ success: Bool, 
    _ image: UIImage?,
    _ indexPath: IndexPath ) -> Void) { ... }

编辑#2

顺便说一下,您真的应该将图像保存到磁盘并从那里加载它们,而不是每次都从互联网上加载。我建议使用图像的散列 URL 作为文件名。

修改loadImageAsync如下:

  1. 检查磁盘上是否已存在该文件。如果是这样,加载它并return它。

  2. 如果文件不存在,执行异步加载,然后使用 URL 的哈希作为文件名将其保存到磁盘,然后 returning in-memory 图片.

因为您的 completionHandler 可以在单元格被下一个用户重新使用后调用,并且可能已经触发了对该单元格的另一个图像请求。事件的顺序 (reuse/completion) 是不可预测的,事实上,较晚的异步请求可能会先于较早的异步请求完成。