我有一个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...
但是,它没有显示任何效果。你知道为什么或有任何其他解决方案来解决这个问题吗?
答案 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) { ... }
顺便说一句,您应该将图像保存到磁盘并从那里加载,而不是每次都从Internet加载。我建议使用图像URL的哈希作为文件名。
修改loadImageAsync,如下所示:
检查磁盘上是否已存在该文件。如果是,请加载并返回。
如果文件不存在,请执行异步加载,然后使用URL的哈希值作为文件名将其保存到磁盘,然后再返回内存中的图像。
答案 1 :(得分:1)
因为在为下一个用户重用该单元格之后可以调用completionHandler,并且可能已经触发了该单元格的另一个图像请求。事件的顺序(重用/完成)是不可预测的,实际上后来的异步请求可以在之前的异步请求之前完成。