我有一个定义如下的自定义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在缓存中,则返回缓存的图像。如果没有,它会启动URLSession以从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()
}
}
任何帮助将不胜感激。如果我错过了任何重要信息,请告诉我。
答案 0 :(得分:0)
这里有很多问题,但我认为只有#1导致你的图像问题根本无法显示。
在设置图像之前检查单元格是否相同的行:collectionView.cellForItem(at: indexPath) == cell
。 cellForItem
实际上是在屏幕外返回nil。它在下载图像时起作用,因为有时间将它带到屏幕上,但是当从缓存中拉出图像时,立即调用completionHandler,因此单元格还没有返回到collectionView!
有多种解决方案,但最简单的方法是添加一个返回到completionHandler的wasCached
标志(请参阅本答案末尾的代码)。
您实际上是在下载每个图像两次:首先,使用URLSession.shared.downloadTask
,然后在从下载任务的网址中获取数据时再次使用completionHandler。您传递给try? Data(contentsOf: url)
的URL是服务器的图像URL,而不是completionHandler中返回的文件URL。
从Apple's documentation of downloadTask(with:completionHandler:)传递到您正在阅读的completionHandler块的网址:
存储服务器响应的临时文件的位置。在完成处理程序返回之前,您必须移动此文件或将其打开以供阅读。否则,文件将被删除,数据将丢失。
如果您不需要磁盘缓存,那么要修复#2和#3,切换到使用dataTask(with:completionHandler:)
功能,它会为您提供已在内存中的图像数据,您可以从中构建图像。 / p>
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
}
}
}