What my requirement is "I have an application that downloads images from the amazon s3 bucket. And I need to cache those images, I used normal cache for doing it. But I need to implement the cache technique same as that of SDWebImage". How the caching method in SDWebImage works.
create a cache and set image to the url string and assign it from anywhere by checking the cache have the object or not
let imageCache = NSCache<AnyObject, AnyObject>()
imageCache.setObject(imageToCache!, forKey: urlString as AnyObject)
if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = imageFromCache
return
}
Pretty Handy UIImageview Extension for image caching.
import UIKit
let imageCache = NSCache<AnyObject, AnyObject()
extension UIImageView {
func cacheImage(urlString: String){
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!) {
data, response, error in
if let response = data {
DispatchQueue.main.async {
let imageToCache = UIImage(data: data!)
imageCache.setObject(imageToCache!, forKey: urlString as AnyObject)
self.image = imageToCache
}
}
}.resume()
}
}
Related
I've been able to solve the issue of caching images to improve scroll performance in my app. However nil is found when it tries to add it to cache. Also how can I add a placeholder image for images that failed to load or aren't available ?
let imageCache = NSCache<NSString, UIImage>()
extension UIImageView {
func downloadImage(from imgURL: String) -> URLSessionDataTask? {
guard let url = URL(string: imgURL) else { return nil }
// set initial image to nil so it doesn't use the image from a reused cell
image = nil
// check if the image is already in the cache
if let imageToCache = imageCache.object(forKey: imgURL as NSString) {
self.image = imageToCache
return nil
}
// download the image asynchronously
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let err = error {
print(err)
return
}
DispatchQueue.main.async {
// create UIImage
let imageToCache = UIImage(data: data!)
// add image to cache
imageCache.setObject(imageToCache!, forKey: imgURL as NSString)
self.image = imageToCache
}
}
task.resume()
return task
}
}
A couple of observations:
Just supply placeholder as parameter to function and use it instead of nil to initialize the image.
Do that after checking the cache (because there’s no point in using the placeholder if you found desired image in the cache).
Avoid use of ! forced unwrapping operator.
Check that UIImage(data:) found an image in the guard statement (and on the session queue, not the main thread).
Thus:
let imageCache = NSCache<NSString, UIImage>()
extension UIImageView {
func downloadImage(from imgURL: String, placeholder: UIImage? = nil) -> URLSessionDataTask? {
guard let url = URL(string: imgURL) else { return nil }
// check if the image is already in the cache
if let imageToCache = imageCache.object(forKey: imgURL as NSString) {
image = imageToCache
return nil
}
// set initial image to placeholder so it doesn't use the image from a reused cell
image = placeholder
// download the image asynchronously
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard
let data = data,
error == nil,
let imageToCache = UIImage(data: data)
else {
print(error ?? URLError(.badServerResponse))
return
}
imageCache.setObject(imageToCache, forKey: imgURL as NSString)
DispatchQueue.main.async {
self.image = imageToCache
}
}
task.resume()
return task
}
}
I'm currently developing an iOS application where I've a lot of images. I use NSCache() for storing the images.
So, every time I load the application the images is being downloaded, saved in cache and stays in the cache all the way till I terminate the application.
I'm looking for a solution where the images will be saved even if you terminate the application, just download if the image does NOT exists in the current NSCache().
This one is used in the beginning of the extension:
let imageCache = NSCache<AnyObject, AnyObject>()
And then I've an extension with a function like this:
extension UIImageView {
func downloadImages(from urlString: NSString){
//Check for cached images and return out if found
if let cachedImage = imageCache.object(forKey: urlString) as? UIImage{
print("cache?", imageCache)
self.image = cachedImage
return
}
//Retrieve the images from Firebase Storage
let url = URL(string: urlString as String)
//Create an URL session
URLSession.shared.dataTask(with: url!) { (data, response, err) in
if let err = err{
print(err.localizedDescription)
}
//Continue on background thread
DispatchQueue.main.async {
//Check if image exists
if let downloadedImage = UIImage(data: data!){
//Add image to cache
imageCache.setObject(downloadedImage, forKey: urlString)
//Set the image to downloaded image.
self.image = downloadedImage
}
}
}.resume()
}
}
The image cache works until I terminate the application. Is there any way I can save the NSCache() even if I terminate the app?
I'm trying to download image and store that image in cache. From next attempt, pull that image from cache if exists otherwise download that image
I've gone thru similar posts but nothing helps me to fix.
Please advice
let imageCache = NSCache<NSString, UIImage>()
func getImage(from url: URL, completion: #escaping ((UIImage?, Error?)->(Void))) {
if let cachedImage = imageCache.object(forKey: url.absoluteString as NSString) {
print("Image from cache")
completion(cachedImage, nil)
}
NetworkOperations().downloadImage(from: url) { (data, error) in
if let error = error {
completion(nil, error)
} else if let imgData = data, let image = UIImage(data: imgData) {
self.imageCache.setObject(image, forKey: url.absoluteString as NSString)
print("Image from cache")
completion(image, nil)
}
}
}
When i call this method, it always picks by downloading image. I'm struck here
Create a shared cache at the application level and add/retrieve/remove wherever you want as below,
class ImageCache {
private init() {}
static let shared = NSCache<NSString, UIImage>()
}
Usage
func getImage(from url: URL, completion: #escaping ((UIImage?, Error?)->(Void))) {
if let cachedImage = ImageCache.shared.object(forKey: url.absoluteString as NSString) {
print("Image from cache")
completion(cachedImage, nil)
}
NetworkOperations().downloadImage(from: url) { (data, error) in
if let error = error {
completion(nil, error)
} else if let imgData = data, let image = UIImage(data: imgData) {
ImageCache.shared.setObject(image, forKey: url.absoluteString as NSString)
print("Image from cache")
completion(image, nil)
}
}
}
I hope, this will fix the issue and it should also give you an insight of what is being wrong with your implementation. Most probably your cache has the same scope as the object where it is declared and used so once the object is de-initialised, the cache will also be released.
In the above implemtation, cache is declared as a static so that it is never de-initialized but you can clear the cache anytime with the following,
ImageCache.shared.removeAllObjects()
Hi i want to cache my images coming from json. I have made a networkService which downloads and parse the json and i use the func downloadImage() which makes http request of the imageurl.Then I update my episode with this func but images are still downloading on scroll
var episode: Product! {
didSet {
self.updateUI()
}
}
let imageCache = NSCache<AnyObject, AnyObject>()
func updateUI()
{
menuItemNameLabel?.text = episode.title
ingredientsItemLabel?.text = episode.summary
priceItemLabel?.text = episode.price
menuItemImageView?.image = UIImage(named: "Koulourades")
if let thumbnailURL = episode.thumbnailURL {
let networkService = NetworkService(url: thumbnailURL)
networkService.downloadImage({ (imageData) in
if let imageFromCache = self.imageCache.object(forKey: self.episode.thumbnailURL as AnyObject) as? UIImage {
DispatchQueue.main.async(execute: {
self.menuItemImageView?.image = imageFromCache
return
})
}
DispatchQueue.main.async(execute: {
let imageToCache = UIImage(data: imageData as Data)
self.imageCache.setObject(imageToCache!, forKey: self.episode.thumbnailURL as AnyObject)
self.menuItemImageView?.image = imageToCache
})
})
}
}//--end updateUI()
The way you need to resolve your problem is called Lazy Loading in which application will download the images at once and cache it into the memory.
There are multiple third party libraries available for caching images.
Libraries like ,
1) HanekeSwift -> https://github.com/Haneke/HanekeSwift
2) Kingfisher -> https://github.com/onevcat/Kingfisher
3) SDWebImage -> https://github.com/rs/SDWebImage
4) AlamofireImage -> https://github.com/Alamofire/AlamofireImage
I'm having problems cacheing for images from JSON correctly with this UIImageView extension. The images load correctly when I first open the app and scroll down the page. However when I scroll back up, they don't reload and are completely gone. Can anyone see anything wrong with the code?
let imageCache = NSCache<AnyObject, AnyObject>()
extension UIImageView {
func loadImageUsingUrlString(urlString: String) {
let url = NSURL(string: urlString)
if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = imageFromCache
return
}
URLSession.shared.dataTask(with: url! as URL) { (data, response, error) in
if error != nil {
print(error ?? "URLSession error")
return
}
DispatchQueue.main.async {
let imageToCache = UIImage(data: data!)
imageCache.setObject(imageToCache!, forKey: urlString as AnyObject)
self.image = imageToCache
}
}.resume()
}
}
Here is the snippet from the cell.swift file
let imageCache = NSCache<AnyObject, AnyObject>()
func setupThumbnailImage() {
if let thumbnailImageUrl = television?.poster_url {
let urlPrefix = "https://www.what-song.com"
let urlSuffix = thumbnailImageUrl
let urlCombined = urlPrefix + urlSuffix
thumbnailImageView.loadImageUsingUrlString(urlString: urlCombined)
}
}
I suggest using kingFisher, it is very easy to use and it manages all starting from cache threads etc.
let imageResource = ImageResource(downloadURL:URL(string: imagePath )!,cacheKey: imagePath )
viewImage.kf.indicatorType = .activity
viewImage.kf.setImage(with: resource)
where imagePath is the url of your image and viewImage is your imageView
Most probably you would be calling it in wrong way.
Remember that in tableView you reuse the cells.
By the time response comes back for the URLSessionTask you would have already scrolled up/down. In that case self.image would be assigned to the currently visible cell.
Please add your cellForRow code in question.