NSCache Doesn't work with all images when loading for the first time
Asked Answered
P

3

5

I'm woking on a project in swift 3.0 where I cache the response from the server by using NSCache as to populate them in a UITableView. However for some reason I'm only seeing few images loading when the app loads for the first time, but if If i scroll and come back I see everything (end of retrieving the response from the server I reload my tableview too, but seems that not the case). I'm not sure what I''m exactly missing here, the code as bellow as to show how I cache the images.

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

extension UIImageView {


    public func imageFromServerURL(urlString: String) {
        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{
                    print(error as Any)


                    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
                    }
                })
            }) .resume()
        }
    }
}
Palawan answered 26/7, 2017 at 1:48 Comment(3)
My prime suspect is imageURLString, as it is global variable and you are comparing it with local one, but you change it for every request. Y ?? make sure this, for what ever purpose you write this (if imageURLString == urlString) is work as par your expectation and for more just check that is your logic works well for only one image ?Intermediacy
I think this code is right, and the images take time to load because they are coming from the server , I think if you do not scroll images will still loadMinorca
No if i don't scroll it wont wok at all :(Palawan
P
3

Here the images are downloading and stored in cache just fine. The problem lies in the updation of tableview cells.

When the table view is loading the cells on to the table the images are not downloaded yet. But once the image is downloaded we have to selectively update the cell so that the image is displayed instantly.

Since you are scrolling , the tableview calls 'cellForRowatIndexpath' again which updates the cell showing the downloaded images while scrolling.

If you still wish to use the extension , I suggest you add the tableView and indexpath as the parameters so that we can call reload specific row and have the view updated instantly.

I have updated the table reload code and structure of the function defined in extension. Let me know how it goes.

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

extension UIImageView {


public func imageFromServerURL(urlString: String, tableView : UITableView, 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{
                print(error as Any)


                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

    tableView.reloadRows(at: [indexpath], with: .automatic)

                }
            })
        }) .resume()
    }
}
Penguin answered 26/7, 2017 at 4:54 Comment(3)
I have a sample project done once for json parsing using simple swift 3. You can refer that here: linkPenguin
Great!! Worked like a charm!!Galanti
But it causes the tableview to scroll to the top each time the cell is loaded. Please help!Galanti
M
8

I think this would be a better approach using subclassing rather than extension, (taking help from Jageen's comment, as we cannot contain stored properties inside extension so we use the idea of encapsulation)

let imageCache = NSCache<AnyObject, AnyObject>()


    class CustomImageView: UIImageView {

            var imageUrlString: String?
            func loadImageUsingUrlString(_ urlString: String) {
                    let url = URL(string: urlString)
                    imageUrlString = urlString
                    image = nil

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

                    URLSession.shared.dataTask(with: url!) { (data, response, error) in
                            if error != nil {
                                    print(error!)
                                    return

                            }

                            DispatchQueue.main.async {

                                    let imageToCache = UIImage(data: data!)
                                    if self.imageUrlString == urlString {
                                           self.image = imageToCache  
                                    }
                                    imageCache.setObject(imageToCache!, forKey: urlString as AnyObject)

                            }

                            }.resume()

            }
    }

-Now use this subclass as the type of imageViews that you are showing on the screen

Minorca answered 26/7, 2017 at 4:2 Comment(2)
This worked perfectly for me, along with guidance from Brian Voong the youtube guy!Mustee
This should be the accepted answer. This is great! +1Misusage
P
3

Here the images are downloading and stored in cache just fine. The problem lies in the updation of tableview cells.

When the table view is loading the cells on to the table the images are not downloaded yet. But once the image is downloaded we have to selectively update the cell so that the image is displayed instantly.

Since you are scrolling , the tableview calls 'cellForRowatIndexpath' again which updates the cell showing the downloaded images while scrolling.

If you still wish to use the extension , I suggest you add the tableView and indexpath as the parameters so that we can call reload specific row and have the view updated instantly.

I have updated the table reload code and structure of the function defined in extension. Let me know how it goes.

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

extension UIImageView {


public func imageFromServerURL(urlString: String, tableView : UITableView, 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{
                print(error as Any)


                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

    tableView.reloadRows(at: [indexpath], with: .automatic)

                }
            })
        }) .resume()
    }
}
Penguin answered 26/7, 2017 at 4:54 Comment(3)
I have a sample project done once for json parsing using simple swift 3. You can refer that here: linkPenguin
Great!! Worked like a charm!!Galanti
But it causes the tableview to scroll to the top each time the cell is loaded. Please help!Galanti
C
0

Saving Images in UIImageView Swift 5 with Xcode 14.1 and above through URLCache :-

class CacheImageView: UIImageView {

    let cachesURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
    var diskCacheURL:URL  {
        self.cachesURL.appendingPathComponent("DownloadCache")
    }
    
    var cache:URLCache  {
        URLCache(memoryCapacity: 10_000_000, diskCapacity: 1_000_000_000, directory: diskCacheURL)
    }
    
    var session:URLSession {
        let config = URLSessionConfiguration.default
        config.urlCache = cache
        return URLSession(configuration: config)
    }
    
    func downloadImageFrom(urlString: String, imageMode: UIView.ContentMode) {
        guard let url = URL(string: urlString) else { return }
        downloadImageFrom(url: url, imageMode: imageMode)
    }
    func downloadImageFrom(url: URL, imageMode: UIView.ContentMode) {
        contentMode = imageMode
            let req = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad)
            self.session.dataTask(with: req) { data, response, error in
                guard let data = data, error == nil else { return }
                DispatchQueue.main.async {
                    let imageToCache = UIImage(data: data)
                    self.image = imageToCache
                }
            }.resume()
    }
}

Uses:

var imageViewAstronomy: CacheImageView = CacheImageView()
imageViewAstronomy.downloadImageFrom(urlString: yourStringUrlOfImage, imageMode: .scaleAspectFit)
Cubiform answered 15/12, 2022 at 4:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.