SimpleImageCache was last updated 4 years ago for iOS 7. Since then, developers have cloned this repo and updated it as Swift has evolved. With Swift 4 the new standard, it is time to dust off this class and use some new paradigms.
Many of the alternative CocoaPods have not made the jump to iOS Swift4 yet, but luckily SimpleImageCache provides all the power you need.
The file UIImageView+SimpleImageCache.swift could be created as follows:
import Foundation
import UIKit
class SimpleImageCache: NSObject, URLSessionTaskDelegate {
static let sharedInstance = SimpleImageCache()
var urlSession: URLSession!
var urlCache = URLCache(memoryCapacity: 64 * 1024 * 1024, diskCapacity: 512 * 1024 * 1024, diskPath: "ImageDownloadCache")
var downloadQueue = Dictionary<URL, [(UIImage?, Error?)->()]>()
override init(){
super.init()
let config = URLSessionConfiguration.default
config.requestCachePolicy = URLRequest.CachePolicy.returnCacheDataElseLoad
config.urlCache = urlCache
self.urlSession = URLSession(configuration: config, delegate: self, delegateQueue: nil)
}
func getImageFromCache(url:URL) -> UIImage? {
guard let urlRequest = self.makeRequest(url: url) else {return nil}
if let response = urlCache.cachedResponse(for: urlRequest) {
if let image = UIImage(data: response.data) {
return image
}
}
return nil
}
func getImage(url:URL, completion:((UIImage?, Error?)->())? = nil) {
guard let urlRequest = self.makeRequest(url: url) else {return}
if let image = self.getImageFromCache(url: url) {
if let c = completion {
DispatchQueue.main.async {
c(image, nil)
return
}
}
} else {
let sv = self
OperationQueue.main.addOperation {
let task = self.urlSession.dataTask(with: urlRequest) { (data, response, error) -> Void in
if let completionHandler = sv.downloadQueue[url] {
if let errorReceived = error {
DispatchQueue.main.async {
completionHandler.forEach{ c in
c(nil, errorReceived)
}
}
return
} else {
if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode >= 400 {
DispatchQueue.main.async {
completionHandler.forEach{ c in
c(nil, NSError(domain: NSURLErrorDomain, code: httpResponse.statusCode, userInfo: nil))
}
}
return
} else if let d = data {
if let r = response {
sv.urlCache.storeCachedResponse(CachedURLResponse(response:r, data:d, userInfo:nil, storagePolicy: URLCache.StoragePolicy.allowed), for: urlRequest)
}
if let image = UIImage(data: d) {
DispatchQueue.main.async {
completionHandler.forEach{ c in
c(image, nil)
}
}
return
}
} else {
DispatchQueue.main.async {
completionHandler.forEach{ c in
c(nil, nil)
}
}
return
}
}
}
}
sv.cancelImage(requestUrl: url)
}
sv.addToQueue(url: url, task: task, completion: completion)
}
}
}
func cancelImage(requestUrl:URL?) {
if let url = requestUrl {
if let index = self.downloadQueue.index(forKey: url) {
self.downloadQueue.remove(at: index)
}
}
}
// MARK: - Private
private func addToQueue(url:URL, task:URLSessionDataTask?, completion:((UIImage?, Error?)->())? = nil) {
if let c = completion {
if self.downloadQueue.keys.contains(url) {
self.downloadQueue[url]?.append(c)
} else {
self.downloadQueue[url] = [c]
}
}
if let t = task, t.state != .running {
t.resume()
}
}
private func makeRequest(url: URL) -> URLRequest? {
return URLRequest(url: url, cachePolicy: URLRequest.CachePolicy.returnCacheDataElseLoad, timeoutInterval: 30.0)
}
private func makeRequest(url: String) -> URLRequest? {
if let u = URL(string: url) {
return self.makeRequest(url: u)
}
return nil
}
}
extension UIImageView {
func setImage(url:String?, placeholderImage:UIImage?, success: @escaping (UIImage?)->()){
guard let url = url, url.length > 5 else {
if let i = placeholderImage {
self.image = i
success(i)
return
}
success(nil)
return;
}
if let u = URL(string: url) {
if let i = SimpleImageCache.sharedInstance.getImageFromCache(url: u) {
self.image = i
success(i)
return
} else if let i = placeholderImage {
self.image = i
}
SimpleImageCache.sharedInstance.getImage(url: u, completion: { imageSrc, error in
if let i = imageSrc {
self.image = i
success(i)
} else {
success(nil)
}
})
}
}
func setImage(url:String?, placeholderImage:UIImage? = nil){
self.setImage(url: url, placeholderImage: placeholderImage){ result in
// Do nothing
}
}
}
So what next? How about we try it on a UIViewController...
import UIKit
class SimpleCacheDemoViewController: UIViewController {
let imageView: UIImageView = UIImageView()
private var _loaded = false
override func viewDidLoad() {
super.viewDidLoad()
if _loaded {return}
_loaded = true
imageView.frame = CGRect(x: (UIScreen.main.bounds.width - 250.0) / 2, y: 32.0, width: 250.0, height: 250.0)
imageView.setImage(url: "https://www.mywebsite.com/myimage.png", placeholderImage: UIImage(named: "loading_image"))
view.addSubview(imageView)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
}
There you have it. The downloaded image will load as soon as the view is loaded. If the image has been downloaded before, and has an extended cache policy from the server, it will load instantly.