I would like to enhance the code below to cache images and only download them if they haven\'t been cached previously. I can\'t seem to find any good examples of how to use
Updated for Swift 4
import UIKit
let imageCache = NSCache<AnyObject, AnyObject>()
class ImageLoader: UIImageView {
var imageURL: URL?
let activityIndicator = UIActivityIndicatorView()
func loadImageWithUrl(_ url: URL) {
// setup activityIndicator...
activityIndicator.color = .darkGray
addSubview(activityIndicator)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
imageURL = url
image = nil
activityIndicator.startAnimating()
// retrieves image if already available in cache
if let imageFromCache = imageCache.object(forKey: url as AnyObject) as? UIImage {
self.image = imageFromCache
activityIndicator.stopAnimating()
return
}
// image does not available in cache.. so retrieving it from url...
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if error != nil {
print(error as Any)
DispatchQueue.main.async(execute: {
self.activityIndicator.stopAnimating()
})
return
}
DispatchQueue.main.async(execute: {
if let unwrappedData = data, let imageToCache = UIImage(data: unwrappedData) {
if self.imageURL == url {
self.image = imageToCache
}
imageCache.setObject(imageToCache, forKey: url as AnyObject)
}
self.activityIndicator.stopAnimating()
})
}).resume()
}
}
Usage:
// assign ImageLoader class to your imageView class
let yourImageView: ImageLoader = {
let iv = ImageLoader()
iv.frame = CGRect(x: 10, y: 100, width: 300, height: 300)
iv.backgroundColor = UIColor(red: 0.94, green: 0.94, blue: 0.96, alpha: 1.0)
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
return iv
}()
// unwrapped url safely...
if let strUrl = "https://picsum.photos/300/300".addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed),
let imgUrl = URL(string: strUrl) {
yourImageView.loadImageWithUrl(imgUrl) // call this line for getting image to yourImageView
}
let imageCache = NSCache<AnyObject, AnyObject>()
extension UIImageView {
func loadImageFromUrl(urlString: String) {
if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage{
self.image = imageFromCache
return
}
Alamofire.request(urlString, method: .get).response { (responseData) in
if let data = responseData.data {
DispatchQueue.main.async {
if let imageToCache = UIImage(data: data){
imageCache.setObject(imageToCache, forKey: urlString as AnyObject)
self.image = imageToCache
}
}
}
}
}
}
One potential solution to this would be to utilize NSCache
to take care of caching. Essentially what you would do is check if you already have the image locally to load from rather than fetching every time before you actually make a request.
Here's one of my implementations, however - it's a subclass rather than an extension:
class CustomImageView: UIImageView {
// MARK: - Constants
let imageCache = NSCache<NSString, AnyObject>()
// MARK: - Properties
var imageURLString: String?
func downloadImageFrom(urlString: String, imageMode: UIViewContentMode) {
guard let url = URL(string: urlString) else { return }
downloadImageFrom(url: url, imageMode: imageMode)
}
func downloadImageFrom(url: URL, imageMode: UIViewContentMode) {
contentMode = imageMode
if let cachedImage = imageCache.object(forKey: url.absoluteString as NSString) as? UIImage {
self.image = cachedImage
} else {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else { return }
DispatchQueue.main.async {
let imageToCache = UIImage(data: data)
self.imageCache.setObject(imageToCache!, forKey: url.absoluteString as NSString)
self.image = imageToCache
}
}.resume()
}
}
}
Additionally, here's a useful resource: https://www.hackingwithswift.com/example-code/system/how-to-cache-data-using-nscache