自定义UICollectionViewCell不从缓存加载

时间:2018-03-22 20:21:55

标签: ios swift uicollectionview uicollectionviewcell

我有一个定义如下的自定义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()
    }

}

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

1 个答案:

答案 0 :(得分:0)

这里有很多问题,但我认为只有#1导致你的图像问题根本无法显示。

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

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

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

  3. Apple's documentation of downloadTask(with:completionHandler:)传递到您正在阅读的completionHandler块的网址:

      

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

  4. 如果您不需要磁盘缓存,那么要修复#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
            }
        }
    }