Home > OS >  Update ImageView in TableViewCell from function called from CellForRowAt
Update ImageView in TableViewCell from function called from CellForRowAt

Time:09-17

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"))
  • Related