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()
}
}
}
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()
}
}
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