mfiore
mfiore

Reputation: 331

Swift Image Download TableView

I'm trying to fix a problem with downloading an image asynchronously in a TableView in Swift. This is my Problem: I download the image from a url asynchronously, but if I scroll quickly the TableView my pictures begin to rotate.(The images alternate until the correct one appears).

This is my Download Async Code and imageCache

let imageCache = NSCache()

//DOWNLOAD Image ASINC
extension UIImageView {

    public func imageFromServerURL(url: String){

        if(imageCache.objectForKey(url) != nil){
            self.image = imageCache.objectForKey(url) as? UIImage
        }else{

            let sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()
            let session = NSURLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil)
            let task = session.dataTaskWithURL(NSURL(string: url)!, completionHandler: { (data, response, error) -> Void in
                if error == nil {

                    dispatch_async(dispatch_get_main_queue(), { () -> Void in
                        if let downloadedImage = UIImage(data: data!) {
                            imageCache.setObject(downloadedImage, forKey: url)
                            self.image = downloadedImage
                        }
                    })

                }
                else {
                    print(error)
                }
            })
            task.resume()
        }

    }
}

and Which I recall in the TableView so:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("record_charts", forIndexPath: indexPath) as! myTableViewCell

            let url_img = "https://image/download.jpg"
            cell.immagine.imageFromServerURL(url_img)


        return cell
    }

This is the gif to show you the problem better Gif Problem

Upvotes: 1

Views: 5482

Answers (3)

Tiko
Tiko

Reputation: 570

You need to add cancellation method in UIImageView extension, and call it or in tableView(_:willDisplay:forRowAt:) or in prepareForReuse() of UITableViewCell

or you can cancel request as in SDWebImage's web cache

Upvotes: 0

hoang Cap
hoang Cap

Reputation: 804

This is due to the reuse mechanism of iOS's table view. You can make some modification to your code to fix this:

class AsyncImageView: UIImageView {

private var currentUrl: String? //Get a hold of the latest request url

public func imageFromServerURL(url: String){
    currentUrl = url
    if(imageCache.objectForKey(url) != nil){
        self.image = imageCache.objectForKey(url) as? UIImage
    }else{

        let sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()
        let session = NSURLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil)
        let task = session.dataTaskWithURL(NSURL(string: url)!, completionHandler: { (data, response, error) -> Void in
            if error == nil {

                dispatch_async(dispatch_get_main_queue(), { () -> Void in
                    if let downloadedImage = UIImage(data: data!) {
                        if (url == currentUrl) {//Only cache and set the image view when the downloaded image is the one from last request
                            imageCache.setObject(downloadedImage, forKey: url)
                            self.image = downloadedImage
                        }

                    }
                })

            }
            else {
                print(error)
            }
        })
        task.resume()
    }

}
}

Note #1: I was whiteboard coding the modification, so not sure if the code has correct syntax.

Note #2: Instead of declaring a new subclass of UIImageView, you can use associated objects.

Note #3: I strongly suggest you use AlamoFireImage, it has a category for UIImageView which is exactly what you need in this case (and future cases too).

Upvotes: 3

Upholder Of Truth
Upholder Of Truth

Reputation: 4711

This is because of cell reuse. I will try to explain. Suppose you have 10 cells each having a different image (Images 1 to 10) but only 5 cells fit on the screen. The table starts to load and the first cell requests image 1 to be put in an image view and that starts happening in the background but the table is scrolled before the background loading of the image finishes and the first cell is scrolled of the screen. Now that cell will be reused let's say by the sixth cell which requests image 6. You background request for image 1 then finishes and as it is still holding a reference to the cell image 1 is put in the image view. Then your background process for image 6 finishes and that replaces the image with the new version. It will be even worse if image 6 finishes loading before image 1 as you then get image 6 put in the cell and it's then replaced by image 1.

What you need to do is implement some method so that when the image is available you can check that it is still the correct one to use. I don't think you are going to be able to do that making the function an extension of ImageView so you probably need some kind of central image provider or something similar.

Upvotes: 1

Related Questions