【问题标题】:How to asynchronous load image from a web-server in UICollectionView using NSCache如何使用 NSCache 从 UICollectionView 中的 Web 服务器异步加载图像
【发布时间】:2019-01-15 12:52:07
【问题描述】:

我在使用 NScache 从 UICollectionView 中的网络服务器加载图像时遇到了一些问题。

问题:

图片显示不正确:

  1. 有时它们不会显示在相应的单元格中

  1. 图像在滚动时发生变化

情况:

  1. 我有 3 个数组在函数 viewDidLoad() 中从 Web 服务器正确加载。这些数组是:vPrice、vTitle 和 vImages_api

  2. 我的单元格自定义类有:

    • 价格标签:cell.lblPrice

    • 标题标签:cell.lblTitle

    • 图片:cell.imgPicture

我相信问题出在函数 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell

这可能与我使用 NSCache 的方式有关,也可能与我使用的方式和使用 DispatchQueue 的时间有关。

代码:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = self.collectionViewPRODUSE.dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath) as! clsCustomCell

    cell.lblPrice.text = vPrice[indexPath.item]
    cell.lblTitle.text = vTitle[indexPath.item]

    cell.layer.borderColor = UIColor.lightGray.cgColor
    cell.layer.borderWidth = 0.5

    DispatchQueue.global(qos: .userInitiated).async {
        //background thread
        let ImageString = self.vImages_api[indexPath.item]
        let imageUrl = URL(string: ImageString)
        let imageData = NSData(contentsOf: imageUrl!)

        // main thread to update the UI
        DispatchQueue.main.async {
            let key1 = self.vImages_api[indexPath.item] as AnyObject
            //if i saved allready my image in cache, then i will load my image from cache
            if let imageFromCache = self.objCache.object(forKey: key1){
                cell.imgPicture.image = imageFromCache as! UIImage
            }
            else{//if my image is not in cache  ......
                        if imageData != nil {
                            let myPicture = UIImage(data: imageData! as Data)
                            cell.imgPicture.image = myPicture                 

                            //save my image in cache
                            self.objCache.setObject(myPicture!, forKey: ImageString as AnyObject)
                        }
            }
        }
    }

    return cell
}

编辑后的代码 - 版本 II:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = self.collectionViewPRODUSE.dequeueReusableCell(withReuseIdentifier: "MyCustomCell", for: indexPath) as! clsCustomCell

    cell.lblPret.text = vPrice[indexPath.item]
    cell.lblTitlu.text = vTitle[indexPath.item]

    cell.layer.borderColor = UIColor.lightGray.cgColor
    cell.layer.borderWidth = 0.5
    let key1 = self.vImages_api[indexPath.item] as AnyObject

    if let imageFromCache = self.objCache.object(forKey: key1){
        cell.imgPicture.image = imageFromCache as! UIImage
    }else{
        DispatchQueue.global(qos: .background).async {
            let ImageString = self.vImages_api[indexPath.item]
            let imageUrl = URL(string: ImageString)
            let imageData = NSData(contentsOf: imageUrl!)
            let  myPicture = UIImage(data: imageData! as Data)
            self.objCache.setObject(poza!, forKey: ImageString as AnyObject)
                DispatchQueue.main.async {
                    if imageData != nil {
                        cell.imgPicture.image = myPicture
                    }
                }
        }

    }

    return cell
}

编辑后的代码 - 版本 III

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = self.collectionViewPRODUSE.dequeueReusableCell(withReuseIdentifier: "celula_custom", for: indexPath) as! clsCustomCell
    cell.lblPret.text = vPrice[indexPath.item]
    cell.lblTitlu.text = vTitle[indexPath.item]

  NKPlaceholderImage(image: UIImage(named: "loading"), imageView: cell.imgPicture, imgUrl:  self.vImages_api[indexPath.item]
    ) { (image11) in
        cell.imgPicture.image = image11
    }
    cell.layer.borderColor = UIColor.lightGray.cgColor
    cell.layer.borderWidth = 0.5

    return cell
}

【问题讨论】:

  • “我有 3 个数组”:糟糕的设计。这 3 个项目需要同步,因此使用一个字典数组或自定义结构,其中一个对象可以保存价格值、标题值和 imageLink 之一。 let imageData = NSData(contentsOf: imageUrl!)不要。这会阻塞线程,即使它在后台。如果缓存中有图像,您应该事先检查。由于您正在做的两件事,您的代码不清楚吗?那真的是你的代码吗?还是两次尝试?
  • 正如你所建议的,我在 DispatchQueue 中使用之前检查了图像是否存储在缓存中。现在,在编辑代码后,在版本 II 中出现了一个新情况:有时图像在相应的单元格中正确显示,并且在滚动时不再更改。但有时,在刷新时,图像仍然显示在错误的单元格中,并且缺少一些相应的描述。
  • 您应该在拨打DispatchQueue.global(qos: .background).async 之前执行cell.imgPicture = nil(或将其替换为占位符)。因为单元格被重复使用,所以在下载开始到结束之间,图像可能有一个较旧的图像。复用是UITableView/UICollectionView的一个重要概念。如果它解决了你的问题,现在大多数错误都被清除了(如果NSData(contentsOf:) 似乎仍然是一种不使用的方法,我更喜欢URLSession 因为错误返回),我建议你使用SDWebImage 或同化这样做等等。

标签: uicollectionview swift4 grand-central-dispatch


【解决方案1】:

试试这个它的工作代码(Swift 4)

func NKPlaceholderImage(image:UIImage?, imageView:UIImageView?,imgUrl:String,compate:@escaping (UIImage?) -> Void){

    if image != nil && imageView != nil {
        imageView!.image = image!
    }

    var urlcatch = imgUrl.replacingOccurrences(of: "/", with: "#")
    let documentpath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
    urlcatch = documentpath + "/" + "\(urlcatch)"

    let image = UIImage(contentsOfFile:urlcatch)
    if image != nil && imageView != nil
    {
        imageView!.image = image!
        compate(image)

    }else{

        if let url = URL(string: imgUrl){

            DispatchQueue.global(qos: .background).async {
                () -> Void in
                let imgdata = NSData(contentsOf: url)
                DispatchQueue.main.async {
                    () -> Void in
                    imgdata?.write(toFile: urlcatch, atomically: true)
                    let image = UIImage(contentsOfFile:urlcatch)
                    compate(image)
                    if image != nil  {
                        if imageView != nil  {
                            imageView!.image = image!
                        }
                    }
                }
            }
        }
    }
}

这样使用:

// Here imgPicture = your imageView and UIImage(named: "placeholder") is Display image brfore download actual image.  
imgPicture.image = nil 
NKPlaceholderImage(image: UIImage(named: "placeholder"), imageView: imgPicture, imgUrl: "Put Here your server image Url Sting") { (image) in }

【讨论】:

  • 当没有NS的Swift版本可用时不要使用NSStuff => NSURL => URL,这也将避免as URL之后。
  • @Larme 现在检查一下。
  • @Eventful 如果此解决方案对您有用,那么不要忘记接受并放弃投票.. 乐于助人:)
  • 非常感谢。有效。但是我有两个问题:1)第一次如果我在图像仍在加载时快速向下滚动,值得注意的是,在某些单元格中,显示的图像正在发生变化,直到所有图片都加载完毕。加载所有照片后,一切正常,我可以向上/向下滚动并完美运行。 2) UIImage(named: "placeholder") -> 在我的情况下 UIImage(named: "loading") 它是在加载实际图片之前显示的通用图片是正确的吗?如果是这样,我使用了动画 gif(像旋转轮一样加载 gif),但加载时没有显示。知道为什么吗?
  • @Eventful 检查更新的答案集 imgPicture.image = nil 就在加载图像之前
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多