重新加载具有图像的 UICollectionViewCell

Posted

技术标签:

【中文标题】重新加载具有图像的 UICollectionViewCell【英文标题】:Reload UICollectionViewCell which has image 【发布时间】:2017-07-27 16:19:46 【问题描述】:

我正在尝试创建类似于 Facebook NewsFeed 的东西,我正在使用自定义 UICollectionViewCell 来显示来自 JSON 的数据(文本/图像)。我有 2 个不同的 API。一个用于文本,另一个用于图像(每个单元格都没有图像)。

所以,首先我从我的 textAPI 将所有文本值输入到我的单元格中并重新加载 myCollectionView。效果很好。

现在对于图片,我正在使用 ImageFetcher 来获取图片,

func ImageFetcher(postId : NSNumber, completion : ((_ image: UIImage?) -> Void)!) 

    var image = UIImage()

    let urlString = "http://myImageAPI/Image/\(postId)"
    let jsonUrlString = URL(string: urlString)
    print(urlString)
    URLSession.shared.dataTask(with: jsonUrlString!)  (data, response, error) in

        do 
            if let jsonData = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String:Any] 
                if let images = jsonData["Image"] as? String 
                    if images == "" 
                        print("No Image")
                     else 
                        let dataDecoded : Data? = Data(base64Encoded: images, options: .ignoreUnknownCharacters)
                        image = UIImage(data: dataDecoded!)!
                        completion(image)
                    
                
            
            else 
                completion(nil)
            
         catch 
            print(error.localizedDescription)
        
    .resume()


要在单元格中显示图像,

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 

// Displaying other elements with text

DispatchQueue.main.async  
        self.ImageFetcher(postId: self.myArray[indexPath.item].id!, completion:  (image) -> Void in
            customCell.mainImage.image = image
        )
// Declared "indexPaths" var indexPaths = [IndexPath]()
// Added this lines
      // let indexPath = IndexPath(item: indexPath.item, section: 0)
      // self.indexPaths.append(indexPath) 
    

return customCell



override func viewDidAppear(_ animated: Bool) 
    super.viewDidAppear(animated)

    self.jsonParsing()


func jsonParsing() 
    //Fetching text data from textAPI

    //DispatchQueue.main.async 
    //      self.collectionView.reloadItems(at: self.indexPaths)
    //

代码可以正常工作,但问题是图像仅在我单击它们时出现。 (我可以在一个单元格中看到空白/白色的 imageView,直到我点击它。只要我点击 imageView,就会出现图像)我觉得它与 DispatchQueue.main.async 有关系,但不确定。

我不想在获取图像后重新加载整个 collectionView。只想重新加载那些有图像的单元格。我发现 collectionView.reloadItemsAtIndexPaths(myArrayOfIndexPaths) 在许多解决方案上,但不知道如何使其在这种情况下工作。任何人都可以在这里帮助我吗?任何帮助将不胜感激。

【问题讨论】:

Take ref from here. 如果您还需要帮助,可以询问。 谢谢!!让我试试看。 你能再解释一下吗 -> 问题是图片只有在我点击它们时才会出现 更新了问题。如果您需要任何其他详细信息,请告诉我。 请添加 textMethods 的完整代码,cellForItem & didSelectRow。您的代码似乎很完美。你不需要重新加载。所以 isuue 在别的地方。 【参考方案1】:

你可以试试这个吗?

DispatchQueue.main.async 
      customCell.mainImage.image = image
 

而不是

customCell.mainImage.image = image

=====================更新====================== >

我最终帮助 Snehal 解决的不仅是图像加载问题,而且还解决了有关集合视图和图像缓存的其他一些问题。我建议他使用像 SDWebImage 这样的第三方库,但发现他的 api 在响应中将图像作为 base64 字符串返回。所以我只是继续清理他的代码并编写我认为这是一个可以帮助他的好字符串点的代码。

import UIKit 

class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout  

    let imageView = UIImageView() 
    lazy var collectionView: UICollectionView =  
        let layout = UICollectionViewFlowLayout() 
        layout.minimumInteritemSpacing = 10 
        layout.minimumLineSpacing = 10 
        layout.scrollDirection = .vertical 

        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) 
        collectionView.delegate = self 
        collectionView.dataSource = self 
        collectionView.register(CustomCell.self, forCellWithReuseIdentifier: NSStringFromClass(CustomCell.self)) 
        collectionView.backgroundColor = .clear 
        return collectionView 
    () 


    override func viewDidLoad()  
        super.viewDidLoad() 
        view.addSubview(collectionView) 
     

    override func viewWillLayoutSubviews()  
         super.viewWillLayoutSubviews() 
         collectionView.frame = view.bounds 
     

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int  
        return 50 
     

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell  
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(CustomCell.self), for: indexPath) as! CustomCell 
        cell.imageUrl = "http://myImageAPI/Image/\(postId)" 
        return cell 
     


    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize  
         return CGSize(width: collectionView.frame.size.width - 2 * 20, height: 100) 
     

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)  

 
 


class CustomCell: UICollectionViewCell  

    let imageView = UIImageView() 
    var imageUrl: String?  
        didSet  
            if let imageUrl = imageUrl, let url = URL(string: imageUrl)  
                dataTask = imageView.loadImage(url: url) 
             

         
     

    var dataTask: URLSessionDataTask? 

    override init(frame: CGRect)  
        super.init(frame: frame) 

        backgroundColor = .white 
        imageView.backgroundColor = UIColor.lightGray 
        imageView.contentMode = .scaleAspectFit 
        contentView.addSubview(imageView) 
     

    required init?(coder aDecoder: NSCoder)  
        fatalError("init(coder:) has not been implemented") 
     

    override func prepareForReuse()  
        super.prepareForReuse() 
        dataTask?.cancel() 
        imageView.image = nil 
     

    override func layoutSubviews()  
        super.layoutSubviews() 
        imageView.frame = bounds 
     
 


extension UIImageView  
    @discardableResult func loadImage(url: URL) -> URLSessionDataTask?  
        if let image = ImageLoadManager.manager.cachedImages[url.absoluteString]  
            self.image = image 
            return nil 
         
        let task = URLSession.shared.dataTask(with: url)  (data, response, error) in 
            do  
                guard let data = data else  
                    return 
                 
                if let jsonData = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:Any]  
                if let images = jsonData["PostImage"] as? String // Pase the Base64 image string
                     if images == ""  
                          print("No Image") 
                      else  
                         if let dataDecoded = Data(base64Encoded: images, options: .ignoreUnknownCharacters), let decodedImage = UIImage(data: dataDecoded)  
                             DispatchQueue.main.async  
                   ImageLoadManager.manager.cachedImages[url.absoluteString] = decodedImage 
                   self.image = decodedImage 
                               
                          
                      
                  
              
             else  
                  DispatchQueue.main.async  
                      self.image = nil 
                   
              
             catch  
                 print(error.localizedDescription) 
             
         
        task.resume() 
        return task 
     
 

class ImageLoadManager  
    static let manager = ImageLoadManager() 
    var cachedImages = [String: UIImage]() 

【讨论】:

天啊!! :|这就是问题所在。啊..谢谢哥们的帮助!! 嘿 HMHero,我又遇到了同样的问题。我已经更新了我的代码。这次我使用缓存的图像来显示。这次在我滚动 collectionView 之前图像不会显示。一旦我滚动所有图像都在正确的位置。 @Snehal 这可能是同一个问题。这个想法是,您必须确保将图像设置为图像视图,它必须位于主线程中。 它在主线程中。 Here 是更新后的代码。 在完成块中设置图像时会怎样?首先将 DispatchQueue.main.async 移动到 ImageFetcher 内部。将 completion(image) 更改为 DispatchQueue.main.async completion(image) 并将 completion(nil) 更改为 DispatchQueue.main.async completion(nil) 。从设置图像中删除 DispatchQueue.main.async。【参考方案2】:

更好的方法是创建一个模型,该类包含图像的 url 和您的文本变量,例如 类 AnyName 变量网址:字符串? var desc:字符串?

init(urlValue: String?,  descValue:String? )
    url = urlValue
    desc = descValue



现在在您的视图控制器中制作上述类名的数组 var items = AnyName 现在通过第一个 api 点击您可以设置此数组中文本的值并传递给 collectionview 并使用文本加载单元格,然后您可以更改 url 的数组值并轻松重新加载 collectionview

【讨论】:

以上是关于重新加载具有图像的 UICollectionViewCell的主要内容,如果未能解决你的问题,请参考以下文章

完成加载后,在 UICollectionView 中的第一个单元格上调用函数

如何在 jQuery 中重新加载/刷新元素(图像)

重新排序单元格后重新加载自定义 UITableViewCell

重新加载单元格中没有图像的表格视图重新加载

使用颤振在移动设备上缓存图像

SwiftUI 列表 - 远程图像加载 = 图像闪烁(重新加载)