skip to Main Content

I have the following code for my tableView:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
        
        // Reset the image in the cell
        cell.coverImageView.image = nil
        
        // Get the recipe that the tableView is asking about
        let recipeInTable = recipe[indexPath.row]
        
        cell.displayRecipe(recipe: recipeInTable, indexPathRow: indexPath.row)

        return cell
    }

This is my custom cell class, where I am caching the image data to pull from:

class CustomCell: UITableViewCell {
    
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var coverImageView: UIImageView!
    
    var recipeToDisplay:Recipe?
    var recipeToPullFrom:Recipe!
    
    func displayRecipe(recipe:Recipe, indexPathRow:Int) {
        
        recipeToPullFrom = recipe
        
        DispatchQueue.main.async {
            self.titleLabel.text = self.recipeToPullFrom.title
            
            if self.recipeToPullFrom.image == nil {
                return
            }
            else {
                let urlString = self.recipeToPullFrom.image

                if let imageData = CacheManager.retrieveData(urlString!) {
                    
                    self.coverImageView.image = UIImage(data: imageData)
                    return
                }
                
                let url = URL(string: urlString!)
                
                guard url != nil else {
                    print("Could not create url object")
                    return
                }
                
                let session = URLSession.shared
                
                let dataTask = session.dataTask(with: url!) { (data, response, error) in

                    if error == nil && data != nil  {

                        CacheManager.saveData(urlString!, data!)

                        if self.recipeToPullFrom.image == urlString {
                            DispatchQueue.main.async {
                                // Display the image data in the imageView
                                self.coverImageView.image = UIImage(data: data!)
                            }
                        }
                        DispatchQueue.main.async {
                            self.coverImageView.image = UIImage(data: data!)
                        }
                    } // End if

                } // End dataTask

                // Kick off the dataTask
                dataTask.resume()
            }
        }
    }
}

And finally my Cache Manager:

class CacheManager {
    
    static var imageDictionary = [String:Data]()
    
    static func saveData(_ url:String, _ imageData:Data) {
        // Save the image data along with the url
        imageDictionary[url] = imageData
    }
    
    static func retrieveData(_ url:String) -> Data? {
        // Return the saved imageData or nil
        return imageDictionary[url]
    }
}

From what I’ve researched, adding the following in my tableView function should have reset the image in my cell before inputting a new one: cell.coverImageView.image = nil.

Is there something I’m missing? I’ve also noticed that only my images are showing in the wrong cells. Could I be doing something incorrect with retrieving the image data from cache?

Any direction or support is much appreciated!

2

Answers


  1. Since your images are downloaded asynchronously then it can be downloaded after the cell been reused for another object. What you can do:

    • When your image is ready to be displayed (downloaded) you need to check if it should be displayed in that cell.

    Or

    • You can also hold a reference to the URLSessionDataTask and cancel the it before reusing the cell by overriding the prepareForReuse method.
    Login or Signup to reply.
  2. I don’t think that you need here an asynchronously call especially, when you using .main in other .main. But If you what so you what so make some thing like

    DispatchQueue.global(qos: .userInteractive).async {
        // Never do here ui code - label, view, images.. anything; or would crash
        DispatchQueue.main.async {
            // To do here you ui updates
        }
    }
    

    here is some refactored code with reuse method. I agree with
    Hach3m. U need to cancel requests if you don’t it anymore, when next cell is about to shown

    class CustomCell: UITableViewCell {
            @IBOutlet private weak var titleLabel: UILabel!
            @IBOutlet private weak var coverImageView: UIImageView!
    
            private var dataTask: URLSessionDataTask?
    
            func displayRecipe(recipe: Recipe, indexPathRow: Int) {
                titleLabel.text = recipe.title enter code here
                guard let urlString = recipe.image else { return }
    
                if let imageData = CacheManager.retrieveData(urlString) {
                    coverImageView.image = UIImage(data: imageData)
                    return
                }
    
                guard let url = URL(string: urlString) else { return print("Could not create url object") }
    
                dataTask = URLSession.shared.dataTask(with: url) { (data, response, error) in
                    guard let data = data else { return self.clear() }
                    CacheManager.saveData(urlString, data)
                    self.coverImageView.image = UIImage(data: data)
                }
    
                dataTask?.resume()
            }
    
            override func prepareForReuse() {
                super.prepareForReuse()
                clear()
            }
    
            private func clear() {
                dataTask?.cancel()
                dataTask = nil
            }
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search