Hibernia Hibernia - 2 months ago 21
iOS Question

Images loading in incorrectly even with cache

if let toID = message.chatPartnerId() {
firebaseReference.child(toID).observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: Any] {


cell.nameLabel.text = dictionary["displayname"] as? String
let pic = dictionary["pictureURL"] as! String
print("THIS IS THE URL FOR EACH DISPLAYNAME")
print(dictionary["displayname"] as? String)
print(pic)


if let imageFromCache = MainPageVC.imageCache.object(forKey: pic as NSString) {

cell.pictureLabel.image = imageFromCache
} else {

let requested = URLRequest(url: URL(string: pic )!)

URLSession.shared.dataTask(with: requested) {data, response, err in


if err != nil {
print(err)
} else {

DispatchQueue.main.async {

let imageToCache = UIImage(data: data!)
MainPageVC.imageCache.setObject(imageToCache!, forKey: pic as NSString)
//cell.pictureLabel.image = nil
cell.pictureLabel.image = imageToCache

}
}

}.resume()
}
}
})
}


return cell
}


I'm running this code in my
cellForRowAtIndexPath
and I'm getting a ton of really bad behavior. I'm also getting similar behavior on other pages but for some reason this block of code with about a 90% consistency returns incorrect information for cells.

I get a lot of duplicate pictures being used,
displaynames
in the wrong places, but when I'm actually clicking into a person, my detail page shows the correct information every single time. That code is the typical
didSelectRowAtIndexPath
and passing the person.

What I don't understand is why on the initial load of this page all of the information is screwed up, but if I click into someone and come back the entire
tableview
has correct names and pictures. The names/pics also fix if I scroll a cell off the screen then come back to it.

I'm getting this behavior all over my app, meanwhile I see caching/loading done like this everywhere. Is it because I'm running the code in my
cellForRowAtIndexPath
? The only difference I see is that I'm running it there instead of creating a function inside of my Person class that configures cells and running it like that. What I don't understand is why that would make a difference because as far as I'm aware running a function within
cellforRowAtIndexpath
would be the same as copy-pasting that same code into there?

Any ideas/suggestions?

Edit: I'm getting a very similar situation when I'm running the following code:

self.PersonalSearchesList = self.PersonalSearchesList.sorted{ $0.users > $1.users }

self.tableView.reloadData()


Where I'm sorting my array before reloading my data. The information sometimes loads in incorrectly at first, but once I scroll the cell off the screen then come back to it it always corrects itself.

Answer

if you are using swift 3 here are some handy functions that allow you to save an image to your apps directory from an URL and then access it from anywhere in the app:

func saveCurrentUserImage(toDirectory urlString:String?) {
    if urlString != nil {
        let imgURL: URL = URL(string: urlString!)!
        let request: URLRequest = URLRequest(url: imgURL)

        let session = URLSession.shared
        let task = session.dataTask(with: request, completionHandler: {
            (data, response, error) -> Void in

            if (error == nil && data != nil) {
                func display_image() {
                    let userImage = UIImage(data: data!)

                    if let userImageData = UIImagePNGRepresentation(userImage!) {
                        let filename = self.getDocumentsDirectory().appendingPathComponent("userImage")
                        try? userImageData.write(to: URL(fileURLWithPath: filename), options: [.atomic])
                    }
                }
                DispatchQueue.main.async(execute: display_image)
            }
        })
        task.resume()
    }
}

and then access it with any view controller using this:

 extension UIViewController {

    func getImage(withName name: String) -> UIImage {
        let readPath = getDocumentsDirectory().appendingPathComponent(name)
        let image = UIImage(contentsOfFile: readPath)
        return image!

    }
}

and finally calling it like this:

cell.pictureLabel.image = getImage(withName: "userImage")

If you can run the saveCurrentUserImage function prior to running cellForRowAtIndexPath then you can just check if the photo is nil in the directory before attempting to download it. You might be getting funny behavior when the page initially loads because you have multiple network calls going on at once. I wouldn't recommend making any network calls in cellForRowAtIndexPath because every time the cells are re-initialized it's going to make that network call for each cell.

Hope it helps!

EDIT: This method of image saving and retrieval is for images that you want to persist. If you want to erase them from memory you'll have to delete them from your directory.