自定义 UICollectionViewCell 未从缓存中加载
Custom UICollectionViewCell not loading from cache
我有一个自定义的 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
}
}
}
}
ImageManager 是一个单例,它包含一组图像的缓存。如果图像 URL 在缓存中,它 returns 缓存图像。如果没有,它会启动一个 URL 会话以从 Firebase 下载图像。
首次加载视图时图像确实会显示,这向我表明此时一切都或多或少地正常工作。然而,当我滚动时,随机图像被加载,有些最终没有被加载,最终所有的单元格都变成空白并且无论如何都不会加载,即使所有内容都保存在缓存中。
这很傻,但这是我的 ImageManager class:
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()
}
}
如有任何帮助,我们将不胜感激。如果我遗漏了任何重要信息,请告诉我。
这里有多个问题,但我相信只有第一个问题导致您的图像根本不显示。
设置图片前检查单元格是否相同时的行:collectionView.cellForItem(at: indexPath) == cell
。 cellForItem
实际上返回 nil,因为它在屏幕外。它在下载图像时起作用,因为有时间将其显示在屏幕上,但是当将图像从缓存中拉出时,您的 completionHandler 会立即被调用,因此单元格尚未返回到 collectionView!
对此有多种解决方案,但也许最简单的是添加一个返回到您的 completionHandler 的 wasCached
标志(请参阅此答案末尾的代码)。
您实际上是在下载每个图像两次:首先,使用 URLSession.shared.downloadTask
,然后当您从下载任务的 url 中获取数据时,再次在 completionHandler 中进行。您传递给 try? Data(contentsOf: url)
的 URL 是来自服务器的图像 URL,而不是在 completionHandler 中返回的文件 URL。
来自 Apple's documentation of downloadTask(with:completionHandler:) 的 URL 传递到您正在读取的 completionHandler 块中:
The location of a temporary file where the server’s response is stored. You must move this file or open it for reading before your completion handler returns. Otherwise, the file is deleted, and the data is lost.
如果您不需要磁盘缓存,那么要修复#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
}
}
}
我有一个自定义的 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
}
}
}
}
ImageManager 是一个单例,它包含一组图像的缓存。如果图像 URL 在缓存中,它 returns 缓存图像。如果没有,它会启动一个 URL 会话以从 Firebase 下载图像。
首次加载视图时图像确实会显示,这向我表明此时一切都或多或少地正常工作。然而,当我滚动时,随机图像被加载,有些最终没有被加载,最终所有的单元格都变成空白并且无论如何都不会加载,即使所有内容都保存在缓存中。
这很傻,但这是我的 ImageManager class:
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()
}
}
如有任何帮助,我们将不胜感激。如果我遗漏了任何重要信息,请告诉我。
这里有多个问题,但我相信只有第一个问题导致您的图像根本不显示。
设置图片前检查单元格是否相同时的行:
collectionView.cellForItem(at: indexPath) == cell
。cellForItem
实际上返回 nil,因为它在屏幕外。它在下载图像时起作用,因为有时间将其显示在屏幕上,但是当将图像从缓存中拉出时,您的 completionHandler 会立即被调用,因此单元格尚未返回到 collectionView!对此有多种解决方案,但也许最简单的是添加一个返回到您的 completionHandler 的
wasCached
标志(请参阅此答案末尾的代码)。您实际上是在下载每个图像两次:首先,使用
URLSession.shared.downloadTask
,然后当您从下载任务的 url 中获取数据时,再次在 completionHandler 中进行。您传递给try? Data(contentsOf: url)
的 URL 是来自服务器的图像 URL,而不是在 completionHandler 中返回的文件 URL。来自 Apple's documentation of downloadTask(with:completionHandler:) 的 URL 传递到您正在读取的 completionHandler 块中:
The location of a temporary file where the server’s response is stored. You must move this file or open it for reading before your completion handler returns. Otherwise, the file is deleted, and the data is lost.
如果您不需要磁盘缓存,那么要修复#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
}
}
}