I try to update an ImageView witch is part of my TableViewCell. The each cell has other countries, and for each cell I want to download the county's flag and show it in the cell´s ImageView, so I use CellForRowAt, ready the cell´s country and call a function which downloads the image of the flag. But I don't get, how I can update the ImageView in the Cell...
Here is my code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell
// Configure the cell...
let stock = stocks[indexPath.row]
// Image downloading
loadCountryImage(country: stock.country)
return cell
}
func loadCountryImage(country: String) {
let url = "https://www.countryflags.io/\(country)/shiny/24.png"
guard let imageURL = URL(string: url) else {
print("no URL found")
return
}
let session = URLSession.shared
session.dataTask(with: imageURL) { imageData, _, _ in
let image = UIImage(data: imageData!)
}.resume()
So now the image is downloaded successfully, but how do I get it in the imageView of the cell?
Kind regards from Germany! Yannik
CodePudding user response:
The short answer is to pass the cell to loadCountryImage
so that you can update the cell image in the closure from your data task. This update needs to be dispatched onto the main queue.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell
// Configure the cell...
let stock = stocks[indexPath.row]
cell.imageView.image = somePlaceholderImage
// Image downloading
loadCountryImage(country: stock.country, in: cell)
return cell
}
func loadCountryImage(country: String, in cell:CustomCell) {
let url = "https://www.countryflags.io/\(country)/shiny/24.png"
guard let imageURL = URL(string: url) else {
print("no URL found")
return
}
let session = URLSession.shared
session.dataTask(with: imageURL) { imageData, _, _ in
guard let data = imageData, let image = UIImage(data: data) else {
return
}
DispatchQueue.main.async {
cell.imageView.image = image
}
}.resume()
}
The long answer is that you should consider caching and since cells are reused, what happens when the table view scrolls? You may fetch an image that is out of date. One approach is to store the country in the cell and check in the closure to see it is still what you expect before you set the image.
You can handle some of this yourself using UITableviewDatasourcePrefetching
and NSCache
var imageCache = NSCache<NSString,UIImage>()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell
// Configure the cell...
let stock = stocks[indexPath.row]
cell.imageView.image = somePlaceholderImage
cell.country = stock.country
// Image downloading
loadCountryImage(country: stock.country, in: cell)
return cell
}
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths {
let stock = stocks[indexPath.row]
if self.cache.object(forKey: stock.country) == nil {
self.loadCountryImage(country: stock.country, in: nil)
}
}
}
func loadCountryImage(country: String, in cell:CustomCell?) {
if let image = self.imageCache.object(forKey: country) {
cell?.imageView.image = image
return
}
let url = "https://www.countryflags.io/\(country)/shiny/24.png"
guard let imageURL = URL(string: url) else {
print("no URL found")
return
}
let session = URLSession.shared
session.dataTask(with: imageURL) { imageData, _, _ in
guard let data = imageData, let image = UIImage(data: data) else {
return
}
DispatchQueue.main.async {
self.imageCache.setObject(image, forKey: country)
if cell?.country == country {
cell?.imageView.image = image
}
}
}.resume()
}
A better answer is probably to look at frameworks like SDWebImage or Kingfisher that do a lot of this for you.
CodePudding user response:
With Existing solution you can pass cell to to your function to set image image when download. but this approach leads to performance issue while scrolling your TableView, because cells are reusing and your cellForRowAtIndexPath called each time when that specific row will be visible in mobile screen window and your download function also got triggered. As cell are reusing you also encounter problem of display same image in multiple cell. the better solution is to use SDWebImage form cocoaPod. you only have to focus on development . rest will be manage by SDWebImage i.e performance ,cache etc Add SDWebImage image in pod file
pod 'SDWebImage'
Install pod by running
pod install
import SDWebImage image in your viewController
import SDWebImage
set image in cellforRowAt by
cell.yourImageViewInCell.sd_setImage(with: URL(string: "http://youserver.com/path/to/image.jpg"), placeholderImage: UIImage(named: "placeholder.png"))