Home > OS >  Wrong Images loading in some of tableView.dequeueReusableCell
Wrong Images loading in some of tableView.dequeueReusableCell

Time:02-12

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!

CodePudding user response:

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.

CodePudding user response:

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
        }
    }
}
  • Related