快速滚动时在 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如下:
检查磁盘上是否已存在该文件。如果是这样,加载它并return它。
如果文件不存在,执行异步加载,然后使用 URL 的哈希作为文件名将其保存到磁盘,然后 returning in-memory 图片.
因为您的 completionHandler 可以在单元格被下一个用户重新使用后调用,并且可能已经触发了对该单元格的另一个图像请求。事件的顺序 (reuse/completion) 是不可预测的,事实上,较晚的异步请求可能会先于较早的异步请求完成。
我有一个包含用户列表及其个人资料图片的 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如下:
检查磁盘上是否已存在该文件。如果是这样,加载它并return它。
如果文件不存在,执行异步加载,然后使用 URL 的哈希作为文件名将其保存到磁盘,然后 returning in-memory 图片.
因为您的 completionHandler 可以在单元格被下一个用户重新使用后调用,并且可能已经触发了对该单元格的另一个图像请求。事件的顺序 (reuse/completion) 是不可预测的,事实上,较晚的异步请求可能会先于较早的异步请求完成。