skip to Main Content

I am successfully fetching images from the API, but I don’t know how to display it in the CollectionViewCell. In the JSON result, the ‘image’ objects have URLs like these:

https://sgp1.vultrobjects.com/kaushal-meme-api/meme_api_prod/memes/1111.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=31MV23N3G46Q2UG2RT1V%2F20230927%2Fsgp1%2Fs3%2Faws4_request&X-Amz-Date=20230927T141906Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=153c918fc506968d7e6a40b47a9f5edf0a37a2571636e7799618b692ed0f6839`
https://sgp1.vultrobjects.com/kaushal-meme-api/meme_api_prod/memes/1152.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=31MV23N3G46Q2UG2RT1V%2F20230927%2Fsgp1%2Fs3%2Faws4_request&X-Amz-Date=20230927T141906Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=0c72145271d0edf6dcb1c0c79bcda46cc1c21ec7643a6baa0b98a6824bbeb36c`

I wrote the URL in my code exactly as it appears. Is that incorrect?

Here is my code:

import UIKit

class MainCollectionViewCell: UICollectionViewCell {
    
    
    @IBOutlet weak var cardImage: UIImageView!
    
    @IBOutlet weak var cardLabel: UILabel!


import UIKit

class MainViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
    
    
    @IBOutlet weak var mainCollectionView: UICollectionView!
    
    var jokes : [WelcomeElement]?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        fetchData()
    }
    
    func fetchData() {
        
        let headers = [
            "X-RapidAPI-Key": "",
            "X-RapidAPI-Host": "programming-memes-images.p.rapidapi.com"
        ]
        
        var request = URLRequest(url: URL(string: "https://programming-memes-images.p.rapidapi.com/v1/memes")! as URL,
                                 cachePolicy: .useProtocolCachePolicy,
                                 timeoutInterval: 10.0)
        request.httpMethod = "GET"
        request.allHTTPHeaderFields = headers
        
        let session = URLSession.shared
        let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
            
            if let error {
                print(error)
            } else {
                if let httpResponse = response as? HTTPURLResponse {
                    print(httpResponse)
                }
                do {
                    let decoder = JSONDecoder()
                    let jokeInfo = try decoder.decode([WelcomeElement].self, from: data!)
                    
                    self.jokes = jokeInfo
                    
                    DispatchQueue.main.async {
                        
                        self.mainCollectionView.reloadData()
                        print(jokeInfo)
                    }
                }catch{
                    print("error while decoding json into swift structure: (error)")
                }
            }
        }
        )
        dataTask.resume()
    }

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        
        return self.jokes?.count ?? 0
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        
        let cell = mainCollectionView.dequeueReusableCell(withReuseIdentifier: "mainCell", for: indexPath) as! MainCollectionViewCell
        
        if let nextJoke = self.jokes?[indexPath.row] {
            
            let joke = nextJoke.image
            
            let jokeWay = URL(string: "https://sgp1.vultrobjects.com/kaushal-meme-api/meme_api_prod/memes/"+joke)
            
            cell.cardImage.image =
        }
        return cell
    }

Here is my model sample:

struct WelcomeElement: Codable {
    let id: Int
    let created, modified: String
    let image: String
    let tags: JSONNull?
    let upvotes, downvotes: Int
}

typealias Welcome = [WelcomeElement]

2

Answers


  1. In my case I’m using third party library Nuke below is the code to display image within each cell of collectionView. Please make sure the view change like setting or downloading images of each cell should be in ‘MainCollectionViewCell.swift’.

    cell.cellImageURL = "https://sgp1.vultrobjects.com/kaushal-meme-api/meme_api_prod/memes/"+joke"
    

    In ‘MainCollectionViewCell.swift’ add below source code.

    var cellImageURL: String {
      didSet {
         if let request: NSURLRequest = NSURLRequest(URL: cellImageURL) {
         Nuke.loadImage(with: request, options: options, into: cardImage, progress: nil) { _ in }
         } else {
           cardImage.image = UIImage(named: "placeholder")
      }
    }
    

    Reason to use third party library is to prevent the cells image from fluctuating within cells collection view. You can also use Kingfisher which is also good.

    Cheers!

    Login or Signup to reply.
  2. In your case, there are several ways to solve this problem.

    The most basic and primitive method:

    In this situation, you can simply use URLSession.shared to download the image and set it to the cell’s UIImageView. In the example below, you can see that I simply send a request with the image’s URL, get the data, and initialize a UIImage using that data.

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = mainCollectionView.dequeueReusableCell(withReuseIdentifier: "mainCell", for: indexPath) as! MainCollectionViewCell
    
        if let nextJoke = self.jokes?[indexPath.row] {
            let joke = nextJoke.image
            let jokeWay = URL(string: "https://picsum.photos/1500/600")
    
            if let jokeWay = jokeWay {
                URLSession.shared.dataTask(with: jokeWay) { data, response, error in
                    if let data = data, let image = UIImage(data: data) {
                        DispatchQueue.main.async {
                            cell.cardImage.image = image
                        }
                    } else {
                        print("Error handling")
                    }
                }.resume()
            }
        }
        return cell
    }
    

    Important notes!

    1. I replaced the URL jokeWay with https://picsum.photos/1500/600
      because I wasn’t sure if I could retrieve an image with the link
      from your request. Just replace it with your link, but first, make
      sure you can actually retrieve an image from that link.

    2. It’s crucial to return to the main thread after the image has been
      downloaded.

    3. There are several critical issues with this solution, which I will
      describe and provide fixes for below.

    PROBLEM #1:

    When scrolling quickly, you’ll notice that the images in the cells flicker (multiple images swap between each other). This happens due to the asynchronous image loading and the cell reuse mechanism in UICollectionView. In short, here’s what’s happening:

    • You start scrolling quickly, and cells get reused.

    • The image-loading code gets executed multiple times for the same cell.

    • The cell’s image changes multiple times after all the loading operations are completed.

    • The final image set in the cell won’t be the last one that was downloaded, but the one that finished downloading first. As a result, you might see an image intended for cell 8 in cell 13.

    You can solve this problem simply without relying on third-party libraries. Solution:

    URLSession.shared.dataTask(with: jokeWay) { [cell, indexPath] data, response, error in
        if let data = data, let image = UIImage(data: data) {
            DispatchQueue.main.async {
                // We add a check for the cell's IndexPath.
                // If the current indexPath of this cell differs from the indexPath at the start of the download, then we shouldn't set the image since another image download might have started for this cell.
                guard collectionView.indexPath(for: cell) == indexPath else {
                    print("Cell matching problem detected")
                    return
                }
                cell.cardImage.image = image
            }
        } else {
            print("Error handling")
        }
    }.resume()
    

    PROBLEM #2:

    Though we’ve addressed the flickering of images in cells, part of the issue remains. This is again due to reuse. We’re not getting a clean cell but one with data from its previous use. As a result, we might see images from other cells that have scrolled out of view. To fix this, you should add the following code to your cell:

    class MainCollectionViewCell: UICollectionViewCell {
    
        @IBOutlet weak var cardImage: UIImageView!
        @IBOutlet weak var cardLabel: UILabel!
    
        // Other code ...
    
        override func prepareForReuse() {
            super.prepareForReuse()
            cardImage.image = nil
            // or
            // cardImage.image = UIImage(named: "any placeholder from your Assets")
        }
    
        // Other code ...
    }
    

    By doing this, we clear the cell of data left over from reuse.

    PROBLEM #3:

    The image loading method I described above is not the most optimal.
    It lacks caching, which, when loading images into a UICollectionView or UITableView, can save a significant amount of device resources, especially internet traffic.
    For this, you can use a library like Kingfisher, Nuke, SDWebImage, and so on.

    Good luck with your learning!

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