进入视图时如何禁用 TableView 单元格图像下载

Posted

技术标签:

【中文标题】进入视图时如何禁用 TableView 单元格图像下载【英文标题】:How to disable TableView cell images download when they get into view 【发布时间】:2018-11-09 12:53:47 【问题描述】:

我在每个单元格内都有一个带有 ImageViews 的 TableView。我希望图像被加载一次并保持这种状态,但似乎图像在进入用户可见区域时被加载(下载,我从外部 API 获取它们)。这似乎是一个延迟加载或类似的东西,我想禁用它,因为如果我向下滚动然后再回来,大多数图像都会放错位置。

TableViewController.swift

cell?.mainChampImageView.image = businessLayer.getChampionThumbnailImage(championId: mainChampion.key)

BusinessLayer.swift

func getChampionThumbnailImage (championId: Int) -> UIImage 
        return dataLayerRiot.getChampionThumbnailImage(championId: championId)
    

DataLayerRiot.swift

func getChampionThumbnailImage (championId: Int) -> UIImage 
        var image: UIImage!

        let urlString = ApiHelper.getChampionThumbnailImageApiLink(championId: championId)
        let url = URL(string: urlString)
        let session = URLSession.shared

        let semaphore = DispatchSemaphore(value: 0)

        session.dataTask(with: url!) (data, response, error) in
            if error != nil 
                print("ERROR")
                semaphore.signal()
            
            else 
                image = UIImage(data: data!)!
                semaphore.signal()
            
        .resume()

        semaphore.wait()
        session.finishTasksAndInvalidate()

        return image
    

任何人都知道如何在它们进入用户可见区域时禁用它们的加载并将它们“存储”?

编辑

我正在使用默认方式将单元格出列

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 

        let cell = tableView.dequeueReusableCell(withIdentifier: "Match", for: indexPath) as? TableViewCell
        ...

有没有更好的方法?

编辑 2

我还需要说明我无法安装库,因为这是一个大学项目,我只能在大学的 MAC 上工作(因为我没有自己的 MAC),因此我无法在没有管理员权限的情况下安装包.

【问题讨论】:

您是否使用dequeueReusableCell,如果是,则可能是导致问题的原因。您可能需要下载图像并缓存它们,必要时拉回正确的图像,也许这会有所帮助***.com/a/50443730/285190 看看 KingFisher [github.com/onevcat/Kingfisher].它的主要目的是下载和缓存图像。 @Flexicoder 我添加了将单元格出列的方式,如果有更好的方法,请告诉我。另外,Alan Sarraf,我无法安装库/包,因为我没有管理员权限。 @CristianG 你可能需要实现prepareForReuse() developer.apple.com/documentation/uikit/uitableviewcell/… @CristianG 很高兴它有帮助 【参考方案1】:

您应该将任务保存在内存中,例如:

let task = = session.dataTask() 

之后您可以通过以下方式在任何地方取消它:

task.cancel()

或者,如果对象会话是URLSession 实例,您可以通过以下方式取消它:

session.invalidateAndCancel()

【讨论】:

我已经查看了差异,但您为什么认为 invalideAndCancel 会比 finishTasksAndInvalidate 更好?【参考方案2】:

尝试SDWebImage 延迟加载UITableViewCellUICollectionViewCell 中的图像。通过cocoapods 将其安装到您的项目中。

它是一种异步内存+磁盘映像缓存,具有自动缓存过期处理。

https://github.com/SDWebImage/SDWebImage

代码:

let urlString = ApiHelper.getChampionThumbnailImageApiLink(championId: championId)
let url = URL(string: urlString)
cell?.mainChampImageView.sd_setImage(with: url, placeholderImage: UIImage(named: "placeholder.png"))

【讨论】:

我无法添加库/包,因为我没有管理员权限。如果您可以在没有它的情况下提出答案,我将不胜感激。 :D【参考方案3】:

听起来你可以从做一些图像缓存中受益。有多种方法可以做到这一点,但从你的例子来看,看起来你不需要麻烦添加整个库来这样做。您可以使用 NSCache 以简单的方式完成。

我创建了一个名为 ImageCache 的类,在本例中它是一个单例,因此整个应用程序都可以访问缓存。

import UIKit

class ImageCache: NSObject 
    static let sharedImageCache = ImageCache()

    // Initialize cache, specifying that your key type is AnyObject
    // and your value type is AnyObject. This is because NSCache requires 
    // class types, not value types so we can't use <URL, UIImage>

    let imageCache = NSCache<AnyObject, AnyObject>()

    // Here we store the image, with the url as the key
    func add(image: UIImage, for url: URL) 
        // we cast url as AnyObject because URL is not a class type, it's a value type
        imageCache.setObject(image, forKey: url as AnyObject)
    


    // This allows us to access the image from cache with the URL as the key
    // (e.g. cache[URL])
    func fetchImage(for url: URL) -> UIImage? 
        var image: UIImage?

        // Casting url for the same reason as before, but we also want the result
        // as an image, so we cast that as well
        image = imageCache.object(forKey: url as AnyObject) as? UIImage
        return image
    

所以现在我们有了一些相对简单的缓存。现在了解如何使用它:

func getChampionThumbnailImage (championId: Int) -> UIImage 
    var image: UIImage!

    let urlString = ApiHelper.getChampionThumbnailImageApiLink(championId: championId)
    let url = URL(string: urlString)

    // Before, downloading the image, we check the cache to see if it exists and is stored.
    // If so, we can grab that image from the cache and avoid downloading it again.
    if let cachedImage = ImageCache.sharedImageCache.fetchImage(for: url) 
        image = cachedImage
        return image
    

    let session = URLSession.shared

    let semaphore = DispatchSemaphore(value: 0)

    session.dataTask(with: url!) (data, response, error) in
        if error != nil 
            print("ERROR")
            semaphore.signal()
        
        else 
            image = UIImage(data: data!)!
            // Once the image is successfully downloaded the first time, add it to 
            // the cache for later retrieval
            ImageCache.sharedImageCache.add(image: image, for: url!)
            semaphore.signal()
        
    .resume()

    semaphore.wait()
    session.finishTasksAndInvalidate()

    return image

重新下载图像的原因是表格视图没有无限的单元格。发生的情况是,当您向下滚动时,屏幕上消失的单元格随后会被回收和重新使用,因此当您向上滚动时,必须再次抓取图像,因为它们已被清空。

您可以通过实施缓存来避免再次下载图像。

另一种避免出现错误图像的方法是在重新下载图像之前将图像视图设置为 nil。例如:

cell?.mainChampImageView = nil
cell?.mainChampImageView.image = businessLayer.getChampionThumbnailImage(championId: mainChampion.key)

以上所有内容,以及确保您正确地将单元格出列应该可以解决您的问题。

【讨论】:

我创建了 ImageCache 类并按照指定使用它,甚至在将图像设置为我需要的图像之前将其设置为 nil,但它仍然具有相同的行为。如果您认为查看整个项目会有所帮助,我可以将 gitlab 存储库公开。我还添加了用于使单元格出列的代码,请告诉我它是否正确。 我已经按照@Flexicoder 的建议使用了 prepareForReuse() 并且它可以工作,但出于性能原因,我仍将使用您展示的缓存方法。非常感谢! 如果你公开回复我很乐意看看。

以上是关于进入视图时如何禁用 TableView 单元格图像下载的主要内容,如果未能解决你的问题,请参考以下文章

当用户没有完全滑动以删除表格视图单元格时,如何禁用/启用编辑按钮?

swift - 快速滚动时如何阻止tableview单元格出现故障和闪烁的图像?

单击tableview单元iphone时如何更改图像

如何使用分页更改 tableview 自定义单元格中的 imageview 图像。

点击 tableview 单元格时如何更改 UIImage 颜色?

如何删除不可见的 tableview 单元格的下载图像。在ios中