safasfmasklfalskf
safasfmasklfalskf

Reputation: 67

Async images change every time while scrolling?

So I'm creating an iOS app that lets you browse through the Unsplash wallpapers and I used UICollectionView to load the images in cells but whenever I scroll through an image, I go back the image changes into a different one.

Here's the code

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! ImageCollectionViewCell

    let downloadQueue = dispatch_queue_create("com.donbytyqi.Papers", nil)

    dispatch_async(downloadQueue) {

        let imageURL = NSURL(string: "https://unsplash.it/200/300/?random")
        let imageData = NSData(contentsOfURL: imageURL!)

        var image: UIImage?

        if imageData != nil {
            image = UIImage(data: imageData!)
        }

        dispatch_async(dispatch_get_main_queue()) {
            cell.imageView.image = image
        }

    }

    return cell
}

Upvotes: 3

Views: 1198

Answers (3)

arturdev
arturdev

Reputation: 11039

Let me explain what is going on actually.
When you scroll and go back you actually see the previously displayed cell with previously downloaded image (because of dequeueReusableCellWithReuseIdentifier:), and you will keep seeing that image until your new image will not downloaded, i.e. until execution of cell.imageView.image = image line.

So, you have to do following:

  1. set cell.imageView.image = nil after dequeueReusableCellWithReuseIdentifier: line, like so:

    let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! ImageCollectionViewCell
    cell.imageView.image = nil;
    //...  
    

    This will remove previously downloaded image from imageView until new image download.

  2. You should use something like SDWebImage or UIImageView+AFNetworking for async image downloading with cache support, because every time that your method is called the images will be downloaded again and again instead of getting cached image, and that is waste of traffic.

Good luck!

Upvotes: 0

Surya Subenthiran
Surya Subenthiran

Reputation: 2217

@toddg solution is correct. But still it have a problem in reusing the cell.

If the cell is reused before the network call completion then it will assign the downloaded image to another cell.

So I changed the code like following.

var imageArray: [UIImage]?
let downloadQueue = dispatch_queue_create("com.donbytyqi.Papers", nil)

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! ImageCollectionViewCell

    if let oldImage: UIImage = imageArray[indexPath.row] {
        cell.imageView.image = oldImage
        return cell
    } else {
        cell.imageView.image = nil;
        downloadImage(indexPath);
    }


    return cell
}

func downloadImage(indexPath: NSIndexPath) {

    dispatch_async(downloadQueue) {

        let imageURL = NSURL(string: "https://unsplash.it/200/300/?random")
        let imageData = NSData(contentsOfURL: imageURL!)

        var image: UIImage?

        if imageData != nil {
            image = UIImage(data: imageData!)
        }

        let cell = self.collectionView .cellForItemAtIndexPath(indexPath) as! ImageCollectionViewCell
        dispatch_async(dispatch_get_main_queue()) {
            cell.imageView.image = image
        }

    }

}

Hope this helps.

Upvotes: 2

toddg
toddg

Reputation: 2916

EDIT: Two things going on:

  1. collectionView.dequeueReusableCellWithReuseIdentifier reuses a cell that has already been created (if there's one available). So you're dequeueing one of your previous cells.

  2. The URL your loading your images from generates a random image each time it is called.

Thus, when you scroll to the point where the first row of your collectionview is off screen, those cells get reused. Then when you scroll back up, the cells are recreated with a new random image from "https://unsplash.it/200/300/?random"

A way of circumventing this would be to keep an array of all your images indexed based on the cell index. Of course, if your images are very big and/or you have a really large collectionView, you may run out of memory.

Take a look at this code that I have mocked up. I have not verified that the code actually works.

//instance var to store your images
var imageArray: [UIImage]?

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! ImageCollectionViewCell

    // Check if we have already loaded an image for this cell index
    if let oldImage: UIImage = imageArray[indexPath.row] {
        cell.imageView.image = oldImage
        return cell
    } else {
        // remove the old image, before downloading the new one
        cell.imageView.image = nil
    }


    let downloadQueue = dispatch_queue_create("com.donbytyqi.Papers", nil)

    dispatch_async(downloadQueue) {

        let imageURL = NSURL(string: "https://unsplash.it/200/300/?random")
        let imageData = NSData(contentsOfURL: imageURL!)

        var image: UIImage?

        if imageData != nil {
            image = UIImage(data: imageData!)

            // Save image in array so we can access later
            imageArray.insert(image, atIndex: indexPath.row)
        }

        dispatch_async(dispatch_get_main_queue()) {
            cell.imageView.image = image
        }

    }

    return cell
}

Upvotes: 2

Related Questions