问题描述
在我的应用程序中,我构建了自己的异步图像加载类.我传入一个对象,然后它检查缓存(NSCache)是否有图像,如果没有,它会检查文件系统是否已经保存了图像.如果图像尚未保存,它将在后台下载图像(NSOperations 帮助).
In my app, I built my own asynchronous image loading class. I pass in a object, then it checks if the cache (NSCache) has the image, if not it will then check the file system if the image is saved already. If the image is not saved already, it will then download the image in the background (NSOperations help).
到目前为止,这很好用,但我在加载图像时遇到了一些小问题.
This works great so far, but I have ran into a few small issues with the table view loading the images.
首先,这是我用来设置表格视图单元格的函数 tableView(tableView:, willDisplayCell:, forRowAtIndexPath:)
First off, this is the function I use to set up the table view cell fromtableView(tableView:, willDisplayCell:, forRowAtIndexPath:)
func configureCell(cell: ShowTableViewCell, indexPath: NSIndexPath) {
// Configure cell
if let show = dataSource.showFromIndexPath(indexPath) {
ImageManager.sharedManager.getImageForShow(show, completionHandler: { (image) -> Void in
if self.indexPathsForFadedInImages.indexOf(indexPath) == nil {
self.indexPathsForFadedInImages.append(indexPath)
if let fetchCell = self.tableView.cellForRowAtIndexPath(indexPath) as? ShowTableViewCell {
func fadeInImage() {
// Fade in image
fetchCell.backgroundImageView!.alpha = 0.0
fetchCell.backgroundImage = image
UIView.animateWithDuration(showImageAnimationSpeed, animations: { () -> Void in
fetchCell.backgroundImageView!.alpha = 1.0
})
}
if #available(iOS 9, *) {
if NSProcessInfo.processInfo().lowPowerModeEnabled {
fetchCell.backgroundImage = image
}
else {
fadeInImage()
}
}
else {
fadeInImage()
}
}
else {
// Issues are here
}
}
else {
// Set image
cell.backgroundImage = image
}
})
...
}
Where "//Issues are here" 注释是我遇到多个问题的地方.
Where "// Issues are here" comment is, that is where I run into multiple issues.
到目前为止,我还没有想出另一种方法来验证图像是否属于单元格,以确定//问题在这里"在哪里.如果我添加
So far, I have not figured out another way to validate that the image belongs to the cell for sure where "// Issues are here" is. If I add
cell.backgroundImage = 图片
cell.backgroundImage = image
那里,然后它解决了有时图像不会显示在表格视图单元格上的问题.到目前为止,我发现的唯一原因是图像返回的速度比我返回表格视图单元格的速度要快,这就是为什么表格视图说该索引路径上没有单元格的原因.
there, then it fixes the issue where sometimes the image will not display on the table view cell. So far the only cause I have found for this is that the image is being returned faster than I can return the table view cell so that is why the table view says there is not a cell at that index path.
但是,如果我在那里添加该代码,那么我会遇到另一个问题!单元格会显示错误的图像,然后它会滞后于应用程序,并且图像会不断切换,甚至只是停留在错误的图像上.
But if I add that code there, then I run into another issue! Cells will display the wrong images and then it lags down the app and the image will constantly switch, or even just stay on the wrong image.
我检查了它在主线程上运行,图像下载和缓存都很好.它只需要表格说该索引路径上没有单元格,我已经尝试为也返回 nil 的单元格获取 indexPath.
I have checked that it runs on the main thread, image downloading and caching is all fine. It just has to do that the table is saying there is no cell at that index path, and I have tried getting an indexPath for the cell which returns also nil.
这个问题的半解决方案是在 viewWillAppear/viewDidAppear 中调用 tableView.reloadData().这将解决问题,但随后我会丢失屏幕上表格视图单元格的动画.
A semi-solution to this problem is called tableView.reloadData() in viewWillAppear/viewDidAppear. This will fix the issue, but then I lose the animation for table view cells on screen.
如果我将图像视图传递给 getImageForShow() 并直接设置它,它将解决此问题,但这不是理想的代码设计.图像视图显然存在,单元格存在,但由于某种原因它不想每次都工作.
If I pass the image view into getImageForShow() and set it directly it will fix this issue, but that is less ideal design of code. The image view obviously exists, the cell exists, but for some reason it doesn't want to work every time.
推荐答案
表格视图重用单元格以节省内存,这可能会导致任何需要执行以显示单元格数据的异步例程出现问题(例如加载图像).如果在异步操作完成时单元格应该显示不同的数据,则应用可能会突然进入不一致的显示状态.
Table views reuse cells to save memory, which can cause problems with any async routines that need to be performed to display the cell's data (like loading an image). If the cell is supposed to be displaying different data when the async operation completes, the app can suddenly go into an inconsistent display state.
为了解决这个问题,我建议在您的单元格中添加一个生成属性,并在异步操作完成时检查该属性:
To get around this, I recommend adding a generation property to your cells, and checking that property when the async operation completes:
protocol MyImageManager {
static var sharedManager: MyImageManager { get }
func getImageForUrl(url: String, completion: (UIImage?, NSError?) -> Void)
}
struct MyCellData {
let url: String
}
class MyTableViewCell: UITableViewCell {
// The generation will tell us which iteration of the cell we're working with
var generation: Int = 0
override func prepareForReuse() {
super.prepareForReuse()
// Increment the generation when the cell is recycled
self.generation++
self.data = nil
}
var data: MyCellData? {
didSet {
// Reset the display state
self.imageView?.image = nil
self.imageView?.alpha = 0
if let data = self.data {
// Remember what generation the cell is on
var generation = self.generation
// In case the image retrieval takes a long time and the cell should be destroyed because the user navigates away, make a weak reference
weak var wcell = self
// Retrieve the image from the server (or from the local cache)
MyImageManager.sharedManager.getImageForUrl(data.url, completion: { (image, error) -> Void in
if let error = error {
println("There was a problem fetching the image")
} else if let cell = wcell, image = image where cell.generation == generation {
// Make sure that UI updates happen on main thread
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// Only update the cell if the generation value matches what it was prior to fetching the image
cell.imageView?.image = image
cell.imageView?.alpha = 0
UIView.animateWithDuration(0.25, animations: { () -> Void in
cell.imageView?.alpha = 1
})
})
}
})
}
}
}
}
class MyTableViewController: UITableViewController {
var rows: [MyCellData] = []
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Identifier") as! MyTableViewCell
cell.data = self.rows[indexPath.row]
return cell
}
}
其他几点说明:
- 不要忘记在主线程上进行显示更新.在网络活动线程上更新可能会导致显示在看似随机的时间(或从不)发生变化
- 确保在执行异步操作时弱引用单元格(或任何其他 UI 元素),以防在异步操作完成之前销毁 UI.
这篇关于UITableViewCell 异步加载图像问题 - Swift的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!