Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionView image flickering

I have a UICollectionView that shows cells at fullscreen (i.e. a gallery). Sometimes, when swiping for the new image, an image other than the right one is displayed for less than a second, than the image is updated. Other times, the wrong image is shown, but if you swipe left and than right (i.e. the image disappear and reappear) than the right image comes up. I don't understand the cause of this behavior. Images are downloaded asynchronously when the collection view needs them. Here is the code:

 let blank = UIImage(named: "blank.png")
 func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
     let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! UICollectionViewCell

    let image = images[indexPath.row]
    if let imageView = cell.viewWithTag(5) as? UIImageView {
        imageView.image = blank
        if let cachedImage = cache.objectForKey(image.url) as? UIImage {
            imageView.image = cachedImage
        } else {
            UIImage.asyncDownloadImageWithUrl(image.url, completionBlock: { (succeded, dimage) -> Void in
                if succeded {
                    self.cache.setObject(dimage!, forKey: image.url)
                    imageView.image = dimage
                } 
            })
        }
    }

    return cell
}

where UIImage.asyncDownloadImageWithUrl is:

extension UIImage {
   static func asyncDownloadImageWithUrl(url: NSURL, completionBlock: (succeeded: Bool, image: UIImage?) -> Void) {
       let request = NSMutableURLRequest(URL: url)
       NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: { (response, data, error) in
           if error == nil {
               if let image = UIImage(data: data) {
                  completionBlock(succeeded: true, image: image)
               }
           } else {
                  completionBlock(succeeded: false, image: nil)
           }

       })
     }
  }

and for the first image shown:

func collectionView(collectionView: UICollectionView, willDisplayCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath) {
    if let index = self.index {
        let newIndexPath = NSIndexPath(forItem: index, inSection: 0)
        self.collectionView.scrollToItemAtIndexPath(newIndexPath, atScrollPosition: UICollectionViewScrollPosition.Left, animated: false)
        self.index = nil
    }
}
like image 650
Phoenix Avatar asked Jan 23 '26 22:01

Phoenix


1 Answers

This is what i think it's happening:

  • Cell A appears
  • Request for image A is made to load on Cell A
  • Cell A disappears from screen
  • Cell A reappears (is reused)
  • Request for image B is made to load on Cell A
  • Request for image A is complete
  • Image A loads on to the Cell A
  • Request for image B is complete
  • Image B loads on to the Cell A

This is happening because you are not keeping track of which image url should load on the completion block.

Try this:

One way to do that is to store the url of the image that should be there in your UICollectionViewCell. To do that, create a subclass:

class CustomCollectionViewCell:UICollectionViewCell {
    var urlString = ""
}

Then:

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! CustomCollectionViewCell

    let image = images[indexPath.row]
    if let imageView = cell.viewWithTag(5) as? UIImageView {
        imageView.image = blank
        cell.urlString = image.url.absoluteString
        if let cachedImage = cache.objectForKey(image.url) as? UIImage {
            imageView.image = cachedImage
        } else {
            UIImage.asyncDownloadImageWithUrl(image.url, completionBlock: { (succeded, dimage) -> Void in
                if succeded {
                    self.cache.setObject(dimage!, forKey: image.url)
                    //
                    // This can happen after the cell has dissapeared and reused!
                    // check that the image.url matches what is supposed to be in the cell at that time !!!
                    //
                    if cell.urlString == image.url.absoluteString {
                        imageView.image = dimage
                    }

                } 
            })
        }
    }

    return cell
}

For a more accurate reply, post the project (or a barebones version of it). It's a lot of work to reproduce your setup and test.

like image 59
Juan Carlos Ospina Gonzalez Avatar answered Jan 26 '26 23:01

Juan Carlos Ospina Gonzalez