ios - 自定义 UICollectionViewCell 未从缓存中加载

标签 ios swift uicollectionview 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()
    }

如有任何帮助,我们将不胜感激。如果我遗漏了任何重要信息,请告诉我。

最佳答案

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

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

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

  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/

相关文章:

ios - iOS 有远程推送通知的预算吗?

ios - WatchKit 如何检查 WKInterfaceSwitch 的当前状态

ios - 是否可以删除应用程序图标角标(Badge),但通知仍保留在通知中心?

ios - 自定义 UICollectionViewCell 自动布局 NSInternalInconsistencyException 错误

ios - UICollectionView 应用程序因 UICollectionViewCell 不符合键值而崩溃

ios - 适用于 IOS 的 Unity IAP — 未找到应用程序收据

ios - fatal error : to change segue

ios - Swift 多线程环境中的写时复制

ios - 具有执行功能和 UI 更新的重复动画

swift - 带有 groupPagingCentered 的 UICollectionViewCompositionalLayout 没有开始居中