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

时间:2017-02-18 21:38:41

标签: ios swift image asynchronous reusability

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

但是,当我滚动得非常快时,一些图片会被加载到错误的单元格中。 为了防止重用问题,我在每次重用后“重置”图像视图:

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

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

嗯,这也是我的想法。

__更新: 因此,我使用Label参数(类型为Any)扩展了该函数,该函数在放入函数时返回。我试图将参数(使用索引路径)与当前索引路径进行比较。实际上,这应该工作 - 不应该吗?!

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

            cell.loader.stopAnimating()

            if (backlabel as! IndexPath == indexPath) {

                //set image...

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

2 个答案:

答案 0 :(得分:5)

问题在于,如果你快速滚动,下载可能需要足够长的时间才能完成,有问题的单元格已经滚动离开屏幕并被回收用于数据模型中的不同indexPath。

诀窍是在完成块中询问表视图中的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

顺便说一句,您应该将图像保存到磁盘并从那里加载,而不是每次都从Internet加载。我建议使用图像URL的哈希作为文件名。

修改loadImageAsync,如下所示:

  1. 检查磁盘上是否已存在该文件。如果是,请加载并返回。

  2. 如果文件不存在,请执行异步加载,然后使用URL的哈希值作为文件名将其保存到磁盘,然后再返回内存中的图像。

答案 1 :(得分:1)

因为在为下一个用户重用该单元格之后可以调用completionHandler,并且可能已经触发了该单元格的另一个图像请求。事件的顺序(重用/完成)是不可预测的,实际上后来的异步请求可以在之前的异步请求之前完成。