自定义 UI 视图单元格未从缓存加载



我有一个自定义UICollectionViewCell,定义如下:

class MomentsCell: UICollectionViewCell {
@IBOutlet weak var imageView: UIImageView!}

我的委托方法如下所示:

override func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier,
for: indexPath) as! MomentsCell
let imageURL = imageURLS[indexPath.row]
self.updateImageForCell(cell: cell,
inCollectionView: collectionView,
withImageURL: imageURL,
atIndexPath: indexPath)
return cell

方法"updateImageForCell"如下所示:

func updateImageForCell(cell: MomentsCell,
inCollectionView collectionView: UICollectionView,
withImageURL: String,
atIndexPath indexPath: IndexPath) {
/*if let url = URL(string: withImageURL) {
cell.imageView.setImageWith(url, placeholderImage: UIImage(named: "placeholder"))
}*/
cell.imageView.image = UIImage(named: "placeholder")
ImageManager.shared.downloadImageFromURL(withImageURL) {
(success, image) -> Void in
if success && image != nil {
// checks that the view did not move before setting the image to the cell!
if collectionView.cellForItem(at: indexPath) == cell {
cell.imageView.image = image
}
}
}
}

图像管理器是一个单一实例,其中包含一定数量的图像的缓存。如果图像 URL 在缓存中,它将返回缓存的图像。如果没有,它会启动网址以从 Firebase 下载图像。

图像确实在首次加载视图时显示,这向我表明此时一切或多或少都正常工作。但是,当我滚动时,加载了随机图像,有些图像最终没有加载,最终所有单元格都变为空白并且无论如何都不会加载,即使所有内容都保存在缓存中。

这是一个嘶哆嗦嗦的东西,但这是我的ImageManager类:

class ImageManager: NSObject {
static var shared: ImageManager { return _singletonInstance }
var imageCache = [String : UIImage]()
// checks the local variable for url string to see if the UIImage was already downloaded
func cachedImageForURL(_ url: String) -> UIImage? {
return imageCache[url]
}
// saves a downloaded UIImage with corresponding URL String
func cacheImage(_ image: UIImage, forURL url: String) {
// First check to see how many images are already saved in the cache
// If there are more images than the max, we have to clear old images
if imageCache.count > kMaxCacheImageSize {
imageCache.remove(at: imageCache.startIndex)
}
// Adds the new image to the END of the local image Cache array
imageCache[url] = image
}
func downloadImageFromURL(_ urlString: String,
completion: ((_ success: Bool,_ image: UIImage?) -> Void)?) {
// First, checks for cachedImage
if let cachedImage = cachedImageForURL(urlString) {
completion?(true, cachedImage)
} else {
guard let url = URL(string: urlString) else {
completion?(false,nil)
return
}
print("downloadImageFromURL")
let task = URLSession.shared.downloadTask(with: url,
completionHandler: { (url, response, error) in
print("downloadImageFromURL complete")

if error != nil {
print("Error (error!.localizedDescription)")
} else {
if let url = URL(string: urlString),
let data = try? Data(contentsOf: url) {
if let image = UIImage(data: data) {
self.cacheImage(image, forURL: url.absoluteString)
DispatchQueue.main.async(execute: { completion?(true, image) })
}
}
}
})
task.resume()
}
}
func prefetchItem(url urlString: String) {
guard let url = URL(string: urlString) else {
return
}
let task = URLSession.shared.downloadTask(with: url,
completionHandler: { (url, response, error) in
if error != nil {
print("Error (error!.localizedDescription)")
} else {
if let url = URL(string: urlString),
let data = try? Data(contentsOf: url) {
if let image = UIImage(data: data) {
self.cacheImage(image, forURL: url.absoluteString)
}
}
}
})
task.resume()
}

}

任何帮助将不胜感激。如果我错过了任何重要信息,请告诉我。

这里有多个问题在起作用,但我相信只有 #1 导致了图像根本不显示的问题。

  1. 在设置图像之前检查单元格是否相同的行:collectionView.cellForItem(at: indexPath) == cellcellForItem实际上返回 nil,因为它在屏幕外。它在下载图像时有效,因为有时间将其显示在屏幕上,但是当将图像从缓存中拉出时,您的 completionHandler 会立即调用,因此单元格尚未返回到集合视图!

    有多种解决方案,但也许最简单的方法是添加一个返回到 completionHandler 的wasCached标志(请参阅本答案末尾的代码)。

  2. 您实际上要下载每个图像两次:首先,使用URLSession.shared.downloadTask,然后在从下载任务的 url 获取数据时再次在 completionHandler 中下载。要传递给try? Data(contentsOf: url)的 URL 是来自服务器的图像 URL,而不是在 completionHandler 中返回的文件 URL。

  3. 从 Apple 的 downloadTask(with:completionHandler:) 文档中,传递到您正在读取的 completionHandler 块的 URL:

    存储服务器响应的临时文件的位置。在完成处理程序返回之前,必须移动此文件或打开它进行读取。否则,文件将被删除,并且数据丢失。

如果您不需要磁盘缓存,那么要修复 #2 和 #3,请切换到使用dataTask(with:completionHandler:)函数,它为您提供内存中已有的图像数据,您可以从中构建图像。

func downloadImageFromURL(
_ urlString: String,
completion: ((_ success: Bool,_ image: UIImage?, _ wasCached: Bool) -> Void)?) {

// First, checks for cachedImage
if let cachedImage = cachedImageForURL(urlString) {
print("Found cached image for URL (urlString)")
completion?(true, cachedImage, true)
} else {
guard let url = URL(string: urlString) else {
completion?(false,nil, false)
return
}
print("downloadImageFromURL (urlString)")

let task = URLSession.shared.dataTask(with: url) { data, response, error in
print("downloadImageFromURL complete (urlString)")
if let error = error {
print("Error (urlString) (error.localizedDescription)")
} else if let data = data, let image = UIImage(data: data) {
self.cacheImage(image, forURL: url.absoluteString)
DispatchQueue.main.async { completion?(true, image, false) }
}
}
task.resume()
}
}

及其用法:

ImageManager.shared.downloadImageFromURL(withImageURL) { success, image, wasCached in
if success && image != nil {
// checks that the view did not move before setting the image to the cell!
if wasCached || collectionView.cellForItem(at: indexPath) == cell {
cell.imageView.image = image
}
}
}

最新更新