0

Following issue: I have some UITableViewCells, in it some basic Images. These images are being downloaded as soon as the cell gets set (as soon as the image URL gets set). I'm only peforming this once. This download is being handled by a function which also uses caching (See function below).

It works quite well, but when there's bad internet connection the different images get all mixed up, and lots of images end up as duplicates in the wrong cells. After some time they will all update and will all be correct. For a closer demo you can download Gesture Cook on the App Store if the issue has not yet been resolved. Then open it with slow internet connection if possible.

Did anyone have the same issue or has a clue what the issue could be?

Here is my download and caching function:

class ImageService {

static let cache = NSCache<NSString, UIImage>()

static func _downloadImage(withURL url:URL, completion: @escaping (_ image:UIImage?)->()) {
    let dataTask = URLSession.shared.dataTask(with: url) { data, responseURL, error in
        var downloadedImage:UIImage?
        
        if let data = data {
            downloadedImage = UIImage(data: data)
        }
        
        if downloadedImage != nil {
            cache.setObject(downloadedImage!, forKey: url.absoluteString as NSString)
        }
        
        DispatchQueue.main.async {
            completion(downloadedImage)
        }
        
    }
    
    dataTask.resume()
}

static func getImage(withURL url:URL, completion: @escaping (_ image:UIImage?)->()) {
    
    // If recipe does not contain an image
    var loadedURL = url
    if url.absoluteString == "$noimage" {
        loadedURL = URL(string: "https://someURLtoSomeDefaultImage.jpeg")!
    }
    
    // Cache image or download it
    if let image = cache.object(forKey: loadedURL.absoluteString as NSString) {
        completion(image)
    } else {
        _downloadImage(withURL: loadedURL, completion: completion)
    }
}

}

Here you can see how a example of a ImageView being set (this is being called inside a cell):

ImageService.getImage(withURL: someURL) { image in
    self.ImageView.image = image
}
Bot Raid
  • 25
  • 5

1 Answers1

0

You are not showing enough of the key code here to give a definitive critique; but the phrase "this is being called inside a cell" is an alarm bell.

You must not associate an image directly with a cell because cells are reused, and indeed may be reused while the download is happening (so now the image goes into the wrong row). All the work must pass thru the data model only. The data model is structured in terms of rows (and sections if there is more than one), and it just supplies the image directly — which might be, at any given moment, for any given row, nothing, or a placeholder, or the downloaded image if it has been downloaded.

The cell, meanwhile, just does its normal clunky direct translation of the data model into visible interface, so that the table view always looks exactly right after a reload, during scrolling, etc.

[Note that the problem of fetching images from the network to populate a table view is already well solved; instead of trying to reinvent this wheel, use an existing solution if you want to get it right.]

matt
  • 515,959
  • 87
  • 875
  • 1,141