2

I'm downloading images from Cloud Storage and they are used throughout my app. I have them cached but I still see some flickering when my tables scroll or are reloaded.

Here's what I have tried:

My cache code:

import UIKit

class ImageService {

    static let cache = NSCache<NSString, UIImage>()

    static func downloadImage(withURL url:URL, completion: @escaping (_ image:UIImage?)->()) {
        let dataTask = URLSession.shared.dataTask(with: url) { data, responseURL, error in
            var downloadedImage: UIImage?

            downloadedImage = nil

            if let data = data {
                downloadedImage = UIImage(data: data)
            }

            if downloadedImage != nil {
                cache.setObject(downloadedImage!, forKey: url.absoluteString as NSString)
            }

            DispatchQueue.main.async {
                completion(downloadedImage)
            }
        }

        dataTask.resume()
    }

    static func getImage(withURL url:URL, completion: @escaping (_ image:UIImage?)->()) {
        if let image = cache.object(forKey: url.absoluteString as NSString) {
            print("IMAGE IS LOADED FROM CACHE!!!!")
            DispatchQueue.main.async {
                completion(image)
            }
        } else {
            print("IMAGE IS LOADED FROM DOWNLOAD!!!!")
            downloadImage(withURL: url, completion: completion)
        }
    }
}

And here is an example in one of my tables:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    cell.imageView.image = nil

    let storage = Storage.storage()
    let storageRef = storage.reference()
    let imageRef = storageRef.child(String(format: "images/%@.jpg", id))

    imageRef.downloadURL { (URL, error) in
        if error == nil {
            DispatchQueue.main.async {
                ImageService.getImage(withURL: URL!, completion: { image in
                   cell.imageView.image = image
              })
           }
        } else {
          cell.imageView.image = UIImage (named: "placeholder")
        }
    }
}

I see that when I first load my app the images are downloaded and then after that they load from cache. But I still see them flicker. Any suggestions to update the above to either cache to memory & disk or to fix the flickering issue?

Solution:

I am using FirebaseUI which has a modified version of SDWebImage to cache based on the Storage Reference.

Luke Irvin
  • 1,179
  • 1
  • 20
  • 39
  • What is id in let imageRef = storageRef.child(String(format: "images/%@.jpg", id))? – El Tomato Mar 11 '19 at 04:45
  • @ElTomato It's a unique ID that's created for each user. – Luke Irvin Mar 11 '19 at 14:22
  • Taking a look at your delegate function, it's possibly your images are re-downloading every time there's a tableView.reload() which would cause flicker. Are you stepping through that code to see if that's what's happening? – Jay Mar 12 '19 at 17:44
  • @Jay yes and they are loading from cache. – Luke Irvin Mar 12 '19 at 19:22
  • I asked if they are re-downloading upon tableView.reloadData() and you responded *yes* and added *loading from cache*. So are they downloading each time *or* are they loading from cache each time *or* something else? I am asking as if they are downloading each time your tableView is refreshed, it just going to flicker - and you have some other logic issue causing that. – Jay Mar 12 '19 at 20:28
  • My apologies. Yes that I stepped through the code and they are loading from cache on reloadData(). – Luke Irvin Mar 12 '19 at 20:32
  • My followup question is; why are you using NSCache? You can download the image stuff it into a var and use it in your app. What's the use case? Also, how large are these images? And the last question/comment; `DispatchQueue.main.async` seems a bit misplaced here - you're queueing up an async task in a non-background thread since you're updating the UI in that delegate function anyway. Just looks weird there. – Jay Mar 14 '19 at 16:41

2 Answers2

1

TBH I dont know if this will work. But I had the same issue, somewhere you have to set the cell.imageview to nil

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// cell.imageView.image = nil (remove)

let storage = Storage.storage()
let storageRef = storage.reference()
let imageRef = storageRef.child(String(format: "images/%@.jpg", id))

imageRef.downloadURL { (URL, error) in
    if error == nil {
        DispatchQueue.main.async {

            ImageService.getImage(withURL: URL!, completion: { image in
               cell.imageView.image = nil //add
               cell.imageView.image = image
          })
       }
    } else {
      cell.imageView.image = nil // Add
      cell.imageView.image = UIImage (named: "placeholder")
    }
}
}
0

You should take the following lines out of the UITableView delegate method.

let storage = Storage.storage()
let storageRef = storage.reference()

And make them class variables.

El Tomato
  • 6,479
  • 6
  • 46
  • 75
  • Still seeing the flickering when my tables reload. – Luke Irvin Mar 12 '19 at 02:50
  • Take the 3rd line (let imageRef = storageRef.child(String(format: "images/%@.jpg", id))) out as well. If that doesn't work, it's something else that you have not mentioned. – El Tomato Mar 12 '19 at 02:55
  • Not sure if I can do that as the id will be unique at indexPath.row in majority of the View Controllers. – Luke Irvin Mar 12 '19 at 03:16