skip to Main Content

I am trying to create something like Facebook NewsFeed where, I am using custom UICollectionViewCell to display data (Text/Image) from JSON. I have 2 different APIs. One for text and another for images(Every cell doesn’t have image).

So, First of all I am getting all text values in to my cells from my textAPI and reloading myCollectionView. That works perfect.

Now for the Images, I am using ImageFetcher to fetch images,

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()

}

To display image in to Cell,

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

Code works without any error but the issue is Images only appears when I click on them. (I can see empty/white imageView in a cell until I click it. As soon as I click on imageView the Image appears) I feel its something with DispatchQueue.main.async but, not sure.

I do not want to reload the whole collectionView after fetching the images. Just want to reload those cells which have Image. I found
collectionView.reloadItemsAtIndexPaths(myArrayOfIndexPaths)
on many solution but don’t know how to make it work in this scenario. Can anyone please help me here? Any help will be much appreciated.

2

Answers


  1. Can you try this?

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

    instead of

    customCell.mainImage.image = image
    

    ===================== Updated ======================

    I ended up helping Snehal not only image load problem but also a couple of other issues with collection view and image caches. I suggested him to use a third party library like SDWebImage but found out his api returns image as base64 string in the response. So I just go ahead and clean up his code and write the code that I think it’s a good string point that might help him.

    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]() 
    }
    
    Login or Signup to reply.
  2. Better way to do this is like make a model that class that contains url of images and your text variables like
    class AnyName {
    var url: String?
    var desc: String?

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

    Now in your viewcontroller make array of above class name
    var items = AnyName
    Now by first api hitting you can set values for text in this array and passed to collectionview and load cells with text and thereafter you can change arrayvalues for url and reload collectionview easily

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search