0

I have looked at a lot of questions and I do not believe this is a result of reusing a cell as the new cells image is correct, but and existing cells image is incorrect and used to be correct. I'll post the images first so the issue is easier to understand.

Before Scrolling

Existing Image Changed on Scroll

enter image description here

I have a collectionView of image cells (similar to Instagrams user page). I'm fetching all of the data from Firebase. I get the first 12 posts on the initial loading of the screen. However, if you scroll down quickly an EXISTING cells image changes to a newly fetched image. I'm not sure why this is happening... Maybe it's a caching issue? The issue only occurs the first time you load the screen. I've tried setting the images to nil like this:

override func prepareForReuse() {
        super.prepareForReuse()
        self.imageView.image = UIImage()
    }  

This didn't help the issue though.

Here is my cellForItemAt:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "imageCell", for: indexPath) as! ImageCell
        cell.indexPath = indexPath
        cell.imageView.downloadImage(from: currentTablePosts[indexPath.row].pathToImage)
        cell.layer.borderWidth = 1
        cell.layer.borderColor = UIColor.black.cgColor
        return cell
    }

Image downloading and caching:

let imageCache = NSCache<NSString, UIImage>()

extension UIImageView {

    func downloadImage(from imgURL: String!) {
        let url = URLRequest(url: URL(string: imgURL)!)

        // set initial image to nil so it doesn't use the image from a reused cell
        image = nil

        // check if the image is already in the cache
        if let imageToCache = imageCache.object(forKey: imgURL! as NSString) {
            self.image = imageToCache
            return
        }

        // download the image asynchronously
        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
            if error != nil {
                // user an alert to display the error
                if let topController = UIApplication.topViewController() {
                    Helper.showAlertMessage(vc: topController, title: "Error Downloading Image", message: error as! String)
                }
                return
            }

            DispatchQueue.main.async {
                // create UIImage
                let imageToCache = UIImage(data: data!)
                // add image to cache
                imageCache.setObject(imageToCache!, forKey: imgURL! as NSString)
                self.image = imageToCache
            }
        }
        task.resume()
    }
}

Firebase Queries:

static func getInitialTablesPosts(tableNumber: String) {
        tableReference.child(tableNumber).queryLimited(toLast: 12).observeSingleEvent(of: .value, with: { snap in
            for child in snap.children {
                let child = child as? DataSnapshot
                if let post = child?.value as? [String: AnyObject] {
                    let posst = Post()
                    if let author = post["author"] as? String, let likes = post["likes"] as? Int, let pathToImage = post["pathToImage"] as? String, let postID = post["postID"] as? String, let postDescription = post["postDescription"] as? String, let timestamp = post["timestamp"] as? Double, let category = post["category"] as? String, let table = post["group"] as? String, let userID = post["userID"] as? String, let numberOfComments = post["numberOfComments"] as? Int, let region = post["region"] as? String {

                        posst.author = author
                        posst.likes = likes
                        posst.pathToImage = pathToImage
                        posst.postID = postID
                        posst.userID = userID
                        posst.fancyPostDescription = Helper.createAttributedString(author: author, postText: postDescription)
                        posst.postDescription = author + ": " + postDescription
                        posst.timestamp = timestamp
                        posst.table = table
                        posst.region = region
                        posst.category = category
                        posst.numberOfComments = numberOfComments
                        posst.userWhoPostedLabel = Helper.createAttributedPostLabel(username: author, table: table, region: region, category: category)

                        if let people = post["peopleWhoLike"] as? [String: AnyObject] {
                            for(_, person) in people {
                                posst.peopleWhoLike.append(person as! String)
                            }
                        }
                        currentTablePosts.insert(posst, at: 0)
                        NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTableCollectionView"), object: nil)
                    } // end of if let
                }
            }
        })
        tableReference.removeAllObservers()
    }

    static func getMoreTablePosts(tableNumber: String, lastVisibleKey: String) {
        print("FIRED...")
        let currentNumberOfPosts = currentTablePosts.count
        print("Number of posts before fetiching ", currentNumberOfPosts)
        print("Oldest post key ", oldestTableKeys[tableNumber] ?? "not set yet", "***********")

        tableReference.child(tableNumber).queryOrderedByKey().queryEnding(atValue: lastVisibleKey).queryLimited(toLast: 12).observeSingleEvent(of: .value, with: { snap in

            for child in snap.children {
                let child = child as? DataSnapshot
                if let post = child?.value as? [String: AnyObject] {
                    if let id = post["postID"] as? String {
                        if id == lastVisibleKey {
                            return
                        }
                    }
                    let posst = Post()
                    if let author = post["author"] as? String, let likes = post["likes"] as? Int, let pathToImage = post["pathToImage"] as? String, let postID = post["postID"] as? String, let postDescription = post["postDescription"] as? String, let timestamp = post["timestamp"] as? Double, let category = post["category"] as? String, let table = post["group"] as? String, let userID = post["userID"] as? String, let numberOfComments = post["numberOfComments"] as? Int, let region = post["region"] as? String {

                        posst.author = author
                        posst.likes = likes
                        posst.pathToImage = pathToImage
                        posst.postID = postID
                        posst.userID = userID
                        posst.fancyPostDescription = Helper.createAttributedString(author: author, postText: postDescription)
                        posst.postDescription = author + ": " + postDescription
                        posst.timestamp = timestamp
                        posst.table = table
                        posst.region = region
                        posst.category = category
                        posst.numberOfComments = numberOfComments
                        posst.userWhoPostedLabel = Helper.createAttributedPostLabel(username: author, table: table, region: region, category: category)

                        if let people = post["peopleWhoLike"] as? [String: AnyObject] {
                            for(_, person) in people {
                                posst.peopleWhoLike.append(person as! String)
                            }
                        }
                        currentTablePosts.insert(posst, at: currentNumberOfPosts)
                        NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTableCollectionView"), object: nil)
                        if let oldestTableKey = oldestTableKeys[tableNumber] {
                            if postID == oldestTableKey {
                                print("returning")
                                print("number of posts on return \(currentTablePosts.count)")
                                return
                            }
                        }
                    } // end if let
                }
            }
        })
        tableReference.removeAllObservers()
    }
DoesData
  • 6,594
  • 3
  • 39
  • 62

1 Answers1

0

* UPDATE *

The image caching used here still had some issues in my experience. I have since moved on to using Kingfisher which is extremely easy to setup and use.

* OLD SOLUTION *

Found a solution based on the answer suggested in the comments.

I modified the extension I am using to cache my images. Although in the future I think I will subclass UIImageView. Here is the modified version of my code.

import UIKit

let userImageCache = NSCache<NSString, UIImage>()
let imageCache = NSCache<AnyObject, AnyObject>()
var imageURLString: String?


extension UIImageView {

    public func imageFromServerURL(urlString: String, collectionView: UICollectionView, indexpath : IndexPath) {
        imageURLString = urlString

        if let url = URL(string: urlString) {
            image = nil
            if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
                self.image = imageFromCache
                return
            }

            URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
                if error != nil{
                    if let topController = UIApplication.topViewController() {
                        Helper.showAlertMessage(vc: topController, title: "Error Downloading Image", message: error as! String)
                    }
                    return
                }

                DispatchQueue.main.async(execute: {
                    if let imgaeToCache = UIImage(data: data!){
                        if imageURLString == urlString {
                            self.image = imgaeToCache
                        }
                        imageCache.setObject(imgaeToCache, forKey: urlString as AnyObject)// calls when scrolling
                        collectionView.reloadItems(at: [indexpath])
                    }
                })
            }) .resume()
        }
    }

    func downloadImage(from imgURL: String!) {
        let url = URLRequest(url: URL(string: imgURL)!)

        // set initial image to nil so it doesn't use the image from a reused cell
        image = nil

        // check if the image is already in the cache
        if let imageToCache = imageCache.object(forKey: imgURL! as AnyObject) as? UIImage {
            self.image = imageToCache
            return
        }

        // download the image asynchronously
        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
            if error != nil {
                // user an alert to display the error
                if let topController = UIApplication.topViewController() {
                    Helper.showAlertMessage(vc: topController, title: "Error Downloading Image", message: error as! String)
                }
                return
            }

            DispatchQueue.main.async {
                let imageToCache = UIImage(data: data!)
                imageCache.setObject(imageToCache!, forKey: imgURL! as AnyObject)
                self.image = imageToCache
            }
        }
        task.resume()
    }

    func downloadUserImage(from imgURL: String!) {
        let url = URLRequest(url: URL(string: imgURL)!)

        // set initial image to nil so it doesn't use the image from a reused cell
        image = nil

        // check if the image is already in the cache
        if let imageToCache = userImageCache.object(forKey: imgURL! as NSString) {
            self.image = imageToCache
            return
        }

        // download the image asynchronously
        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
            if error != nil {
                // user an alert to display the error
                if let topController = UIApplication.topViewController() {
                    Helper.showAlertMessage(vc: topController, title: "Error Downloading Image", message: error as! String)
                }
                return
            }

            DispatchQueue.main.async {
                // create UIImage
                let imageToCache = UIImage(data: data!)
                // add image to cache
                userImageCache.setObject(imageToCache!, forKey: imgURL! as NSString)
                self.image = imageToCache
            }
        }
        task.resume()
    }
}

I created the first method to cache the images for the collectionView and the other methods are still used for tableViews. The cache was also changed from let imageCache = NSCache<NSString, UIImage>() to let imageCache = NSCache<AnyObject, AnyObject>()

DoesData
  • 6,594
  • 3
  • 39
  • 62