1

I'm using Firebase to load my images into my cell's ImageView but! there is a problem. I'm using caching mechanism for preventing images to redownload when reusable cells loads but the images do not load until i've scroll my TableView.

Here is the code;

     public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
    cell.imgv.image = nil

if let urlString =  self.filteredFlats[indexPath.row].flatThumbnailImage?.imageDownloadURL
    {
        imageURLString = urlString

        let url = URL(string: urlString)
        if let imagefromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage
        {
            DispatchQueue.main.async {
                cell.imgv.image = imagefromCache
                return
            }
        }
        else
        {

            URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
                DispatchQueue.main.async {

                    let imagetoCache = UIImage(data:data!)

                    if self.imageURLString == urlString
                    {
                        cell.imgv.image = imagetoCache

                    }
                    self.imageCache.setObject(imagetoCache!, forKey: urlString as AnyObject)

                }
            }).resume()
        }
    }
    return cell
}

I think the problem is, when URLSession downloaded the image and set it into ImageView, but the loaded image is not showing up until the indexpath.row called back when scrolling up and down.

I realized that the cause of it is this if statement;

 if self.imageURLString == urlString

But if i remove it, the cells getting worse coz of reusable cell's behaviour. Some cells still showing the old cached images.

Really does not aware of what is going on. Was not expecting that TableViews are kind of complex structure as it is.

Thanks.

Shobhakar Tiwari
  • 7,862
  • 4
  • 36
  • 71
L'lynn
  • 167
  • 1
  • 13

3 Answers3

0

I am guessing that imageURLString is a field of your class (a UIViewController maybe). That means that imageURLString = urlString will change its value each time you go in your method.

When the table is rendered, it will call the method for each cell, each time triggerring a download if the image needed is not in the cache. But after those calls, the value of imageURLString will be the url used for the last cell, so only this cell will actually use the image wich has been loaded.

If you want to fix your code, you should have one imageURLString per cell, for example by moving the field to the cell class (create a UITableViewCell subclass in this case), and replace imageURLString and self.imageURLString by cell.imageURLString

Zoleas
  • 4,869
  • 1
  • 21
  • 33
0

I believe the imageCache must be global and should not placed within inside the tableView(_:cellForRowAt:).

Instead, tuck everything in a Custom UIImageView Class except for a var imageCache = String: UIImage. Leave that global.

Change all UIImageView's that will use the loadImage(urlString: String) function that is inside this CustomImageView class to a CustomImageView!.

Example:

@IBOutlet weak var userImage: UIImageView!

change to

@IBOutlet weak var userImage: CustomImageView!

import UIKit

// global var
var imageCache = [String: UIImage]()

class CustomImageView: UIImageView {

    var lastURLUsedToLoadImage: String?

    func loadImage(urlString: String) {
        lastURLUsedToLoadImage = urlString

        self.image = nil

        if let cachedImage = imageCache[urlString] {
            self.image = cachedImage
            return
        }

        guard let url = URL(string: urlString) else { return }

        URLSession.shared.dataTask(with: url) { (data, response, err) in
            if let err = err {
                print("Failed to fetch post image:", err)
                return
            }

            if url.absoluteString != self.lastURLUsedToLoadImage {
                return
            }

            guard let imageData = data else { return }

            let photoImage = UIImage(data: imageData)

            imageCache[url.absoluteString] = photoImage

            // update the UI
            DispatchQueue.main.async {
                self.image = photoImage
            }

            }.resume()
    }
}

Example of calling the load method:

cell.userImage.loadImage(urlString: userImageUrl)
  • It's not a good idea to use global variables. I suggest making a singleton like this. class Caches { static let shared = Caches() var imageCache = [String: UIImage]() } Then you can use it like this. Caches.shared.imageCache.setObject( etc. – Jeff Feb 01 '19 at 16:43
0

I solve it with the following method which is learned from an iOS tutorial on YouTube. We can create a CustomImageView class, and let imageCache = NSCache<AnyObject, AnyObject>() to save the image Cache.When scrolling the tableview,if there is an imageCache for the imageView, we use it directly.But if it doesn't exsit, we will download the image with image URL string and save it.

import UIKit

let imageCache = NSCache<AnyObject, AnyObject>()

class  CustomImageView: UIImageView {

    var imageUrlString :String?

    func loadImageWithString(_ urlString:String){

        imageUrlString = urlString

        let request = URLRequest(url: URL(string:urlString)!)

        self.image = nil

        //imageCache exist,and use it directly
        if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage {

            self.image = imageFromCache
        }

        //imageCache doesn't exist,then download the image and save it
        URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in

            guard let data = data, error == nil else {
                print(error?.localizedDescription as Any)
                return
            }

            DispatchQueue.main.async() { 

                let imageToCache = UIImage(data: data)

                imageCache.setObject(imageToCache!, forKey: urlString as String as AnyObject )

                if self.imageUrlString == urlString{

                    self.image = imageToCache
                }
            }

        }).resume()

    }

}

And in the custom cell Class file, we can use it as follows:

 if let imageUrlString = thumbnailUrlString {
               thumbnailImageView.loadImageWithString(imageUrlString)
            }

Note that 1,thumbnailImageView :CustomImageView 2, thumbnailUrlString is optional