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
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’.
In ‘MainCollectionViewCell.swift’ add below source code.
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!
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’sUIImageView
. In the example below, you can see that I simply send a request with the image’s URL, get the data, and initialize aUIImage
using that data.Important notes!
I replaced the URL
jokeWay
withhttps://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.
It’s crucial to return to the main thread after the image has been
downloaded.
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:
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:
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!