OGeek|极客世界-中国程序员成长平台

标题: ios - 自定义 UICollectionViewCell 未从缓存加载 [打印本页]

作者: 菜鸟教程小白    时间: 2022-12-11 19:54
标题: ios - 自定义 UICollectionViewCell 未从缓存加载

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

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

我的委托(delegate)方法如下所示:

    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 下载图像。

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

这很糟糕,但这是我的 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()
    }

}

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



Best Answer-推荐答案


这里有多个问题,但我相信只有 #1 会导致您的图像根本不显示的问题。

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

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

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

  3. 来自 Apple's documentation of downloadTask(with:completionHandler传递到您正在读取的 completionHandler block 中的 URL:

    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
        }
    }
}

关于ios - 自定义 UICollectionViewCell 未从缓存加载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49437813/






欢迎光临 OGeek|极客世界-中国程序员成长平台 (http://ogeek.cn/) Powered by Discuz! X3.4