Swift: image cache not working as expected - swift

I am trying to use image cache in swift but nothing works as expected. I have tried NSCache and AlamofireImage but both didn't work for me. I did not get any error with code but when I tried to fetch image from cache it always returns nil.
I guess the image is not saved in cache. Can someone here share some links with example on how to use image caching in swift or ios?

What you can do is download images in background thread. When image is downloaded save it an Object and update you'r main thread too. For displaying image next time you can directly load image from the cache.
Below I'm sharing my code
var imageCache = NSMutableDictionary()
if let img = imageCache.valueForKey("key") as? UIImage{
imageView.image = img
}
else{
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(NSURL(string: imgURL!)!, completionHandler: { (data, response, error) -> Void in
if(error == nil){
let img = UIImage(data: data!)
imageCache.setValue(img, forKey: "key") // Image saved for cache
dispatch_async(dispatch_get_main_queue(), { () -> Void in
//Update your image view
})
}
})
task.resume()
}
Hope so this may help you

You can use this as a disk and memory cache, if you implemented downloading image mechanism. You can specify cache size, expire time
https://github.com/huynguyencong/DataCache
Or you can use Kingfisher as a image caching library

Related

How to save multi images from URLs to local storage in swift?

I am developing Restaurant app.
There are 340 foods data.
I am getting this data from backend that developed with Laravel.
App is working well in online.
But, although network is turned off, All foods data should be displayed in app.
So, I tried to save foods data to local.
It is good to save text data to local(exactly, UserDefaults and FileSystem).
But, when I try to save images to local from urls, It occur error and don't save to local exactly.
Have you ever seen such problems?
If yes, I appreciate your help.
Thanks
I don't know exactly what error you faced, but I think the answer to the link can help you.
How do I make JSON data persistent for offline use (Swift 4)
Additionally, image data would be good to be cached.
If you communicate based on URLSession, you can process caching as below.
Saving an Image to a Cache
Cache.imageCache.setObject(image, forKey: url.absoluteString as NSString)
Bring up cached images
let cacheImage = Cache.imageCache.object(forKey: url.absoluteString as NSString)
Code
class Cache {
static let imageCache = NSCache<NSString, UIImage>()
}
extension UIImageView {
func imageDownload(url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
if let cacheImage = Cache.imageCache.object(forKey: url.absoluteString as NSString) {
DispatchQueue.main.async() { [weak self] in
self?.contentMode = mode
self?.image = cacheImage
}
}
else {
var request = URLRequest(url: url)
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request) { data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else {
print("Download image fail : \(url)")
return
}
DispatchQueue.main.async() { [weak self] in
print("Download image success \(url)")
Cache.imageCache.setObject(image, forKey: url.absoluteString as NSString)
self?.contentMode = mode
self?.image = image
}
}.resume()
}
}
}
To make caching more convenient, use a library called Kingfisher.
Kingfisher
You need to manage lazy loadings, you can use SDWebImages instead of manage it manually
SDWebImages will automatically manage your images caches and will also load them without internet here is a simple usage of it
add pod in podfile
pod 'SDWebImage'
usagae:
import SDWebImage
imageView.sd_setImage(with: URL(string: "http://www.example.com/path/to/image.jpg"), placeholderImage: UIImage(named: "placeholder.png"))

SwiftUI UIImage from path not visible

I have an image stored inside an AppGroup, but I'm unable to show the image and I'm not sure why.
I have this inside my view:
Image(uiImage: getImageFromDir(imageName: name)!)
.resizable()
I get the image using the following function:
func getImageFromDir(imageName: String) -> UIImage? {
let appGroupPath = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.myId")!
let imagePath = appGroupPath.appendingPathComponent(imageName)
do {
let imageData = try Data(contentsOf: imagePath)
return UIImage(data: imageData)
} catch {
print("Error loading image : \(error)")
}
return nil
}
This runs fine and the catch block is never hit, but the image still isn't visible. My initial thought was that I had an invalid path, but this doesn't seem the case since I can load the image as expected in React Native using the path.
There's also nothing wrong with my styles since a different image loaded from Assets.xcassets works fine.
Assuming the file is really existed at specified location (you can verify generated URL for that) try with security scoped resource wrapper, like below
func getImageFromDir(imageName: String) -> UIImage? {
let appGroupPath = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.myId")!
let imagePath = appGroupPath.appendingPathComponent(imageName)
do {
if imagePath.startAccessingSecurityScopedResource() { // << this !!
defer {
imagePath.stopAccessingSecurityScopedResource() // << and this !!
}
let imageData = try Data(contentsOf: imagePath)
return UIImage(data: imageData)
}
} catch {
print("Error loading image : \(error)")
}
return nil
}
While my solution is working, it is NOT a valid answer to why my images aren't showing and I would still like to know why, if anyone knows who comes across this post in the future.
To solve this, instead of using an absolute path to the image, I used a base64 string to use as the data. The image now succesfully shows.

how to get image from Json in Swift

i'm a baby of xcode developer, and i really need a help. Below is one of my json data, that i have print in output, for the text i already got display into my screen, but now i'm trying to get the image from the server, and i don't know how to do it.
JSON :
"MoviePhotoL" : "\/Data\/UploadFile\/cnymv-01_1.jpg",
"MoviePhotoP" : "\/Data\/UploadFile\/cnymv-02_1.jpg"
XCODE:
let userImage = iP["MoviePhotoP"] as? String
cell.imageView.image = userImage (??????)
i know that String cannot be converted into UIImage, and i already try to convert it to NSData and convert the NSData to UIImage(data), but still not get the picture :'(.... can somebody please help me?? i really need some help
Those paths seem relative to another source.
You need to generate or get an absolute URL that will let you access the image.
Right now you have a simple string and that's all, you can't convert this to data or image.
You need a string that you can put in a browser and load an image.
Once you're able to do that, you can load the image in your app.
Example:
func getImage(from string: String) -> UIImage? {
//2. Get valid URL
guard let url = URL(string: string)
else {
print("Unable to create URL")
return nil
}
var image: UIImage? = nil
do {
//3. Get valid data
let data = try Data(contentsOf: url, options: [])
//4. Make image
image = UIImage(data: data)
}
catch {
print(error.localizedDescription)
}
return image
}
//1. Get valid string
let string = "https://images.freeimages.com/images/large-previews/f2c/effi-1-1366221.jpg"
if let image = getImage(from: string) {
//5. Apply image
cell.imageView.image = image
}
NOTE: Data(contentsOf:options:) is synchronous and can reduce performance. The larger the image, the longer it will lock it's thread.
Generally you would do such intensive tasks in a background thread and update UI on the main thread, but... to keep this answer simple, I chose not to show that.

Swift 2 Parse and KingFisher cache images

I'm using KingFisher https://github.com/onevcat/Kingfisher
library so i can cache the images and if there is anyone familiar with it i want some hints.
So i have the following code
let myCache = ImageCache(name: recipesClass.objectId!)
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let optionInfo: KingfisherOptionsInfo = [
.DownloadPriority(0.5),
.CallbackDispatchQueue(queue),
.Transition(ImageTransition.Fade(1)),
.TargetCache(myCache)
]
if let imageFile = recipesClass[RECIPES_COVER] as? PFFile {
let URL = NSURL(string: imageFile.url!)!
cell.coverImage.kf_setImageWithURL(URL, placeholderImage: nil,
optionsInfo: optionInfo,
progressBlock: { receivedSize, totalSize in
print("\(indexPath.row + 1): \(receivedSize)/\(totalSize)")
},
completionHandler: { image, error, cacheType, imageURL in
print("\(indexPath.row + 1): Finished")
})
} else {
cell.coverImage.image = UIImage(named:"logo")
}
When i first enter the View it loads normally the images with this good anymation. But i also have a refresh button which makes a query to Parse and it checks if there is any new Recipe and then it reloads the data from the collection view and it prints "Finished"
Does this means that it downloads the images again? Or it loads them from Cache??
I'm asking because it appends the images in a different way inside the cells rather than the first time that it loads.
Any idea?
P.S. what i want to do is that in each cell i want to cache the image with the object ID of each recipe so when the cell loads and it has the image cached with this unique object id, to load it from cache and not to download it.
try this code:
var imageView:UIImageView!
let mCa = ImageCache(name: "my_cache")
let imagePath = getImagePath("image url")
imageView.kf_setImageWithURL(NSURL(string: imagePath)!,placeholderImage: UIImage(named: "DefaultImageName"),optionsInfo: [.TargetCache(mCa)])

How to load image in swift using Alamofire

I have a collection view controller, which load image Async by URL.
(Something like Instegram)
I am looking for the best way to implement the part of the loading image.
please tell me what do you think
First way - without any external library:
let downloadQueue = dispatch_queue_create("com.pro.asyncImages",nil)
dispatch_async(downloadQueue){
var data = NSData(contentsOfURL: NSURL(string: pictureUrl!)!)
var image: UIImage?
if (data != nil){
image = UIImage(data: data!)
}
dispatch_async(dispatch_get_main_queue()){
uiImageView.image = image
}
}
Second way - using Alamofire (the request belong to Alamofire)
if let mediaUrl = info.mediaUrl {
request(.GET,mediaUrl).response(){
(_, _, data, _) in
let image = UIImage(data: data! as NSData)
imageView.image = image
}
}
And lastly, I read that AFNetworking, is doing a great job in loading url async to urlImage, Which this is what I want to do.
So is AFNetworking is better for this task? (then Alamofire)
and if not, I did not understand how to add AFNetworking to my swift project, beside adding the #import “AFNetworking.h” which files do I need to add?
please explain which method is the best, my needs are performance, accusation, caching.
the view collection controller acts like Instegram and is loading images, while scrolling down.
I hope I was clear enough about what I need,
thank you
As authors mention in Alamofire README.md:
When should I use AFNetworking?
UIKit extensions, such as asynchronously loading images to UIImageView
So answer to your question :
So is AFNetworking is better for this task?
Yes!
But if still want to use vanilla Alamofire project, you can fetch image this way:
Alamofire.request(.GET, "https://robohash.org/123.png").response { (request, response, data, error) in
self.myImageView.image = UIImage(data: data, scale:1)
}
P.S.
But if you just want to load image (of course async) - you don't need to use Alamofire or AFNetworking at all.
Just add this small extension in your code:
extension UIImageView {
public func imageFromUrl(urlString: String) {
if let url = NSURL(string: urlString) {
let request = NSURLRequest(URL: url)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {
(response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
self.image = UIImage(data: data)
}
}
}
}
And use it:
myImageView.imageFromUrl("https://robohash.org/123.png")
I created a Swift version of UIImageView+AFNetworking here : Github.
You don't have to use AFNetworking (and a bridging header) or Alamofire in your project. Just add the file to your project and use it.
Example usage:
myImageView.setImageWithUrl(imageUrl, placeHolderImage: somePlaceHolderImage)
Taken from Alamofire/README.md
"In order to keep Alamofire focused specifically on core networking implementations, additional component libraries have been created by the Alamofire Software Foundation to bring additional functionality to the Alamofire ecosystem.
AlamofireImage - An image library including image response serializers, UIImage and UIImageView extensions, custom image filters, an auto-purging in-memory cache and a priority-based image downloading system."
https://github.com/Alamofire/AlamofireImage
In swift 5 & Alamofire 5,You can use like bellow..
AF.request( "https://robohash.org/123.png",method: .get).response{ response in
switch response.result {
case .success(let responseData):
self.myImageView.image = UIImage(data: responseData!, scale:1)
case .failure(let error):
print("error--->",error)
}
}
If you want to use AFNetworking's UImageView framework, you have to do #import "UIImageView+AFNetworking.h" in your bridge-header file.
AFNetworking has a shared cache, so you won’t need to manually cache or cancel your requests. The setImageWithURL from AFNetworking can easily convert a URL to an image in ImageView.
Sample code using UIImage+AFNetworking:
cell.imageView.setImageWithURL(NSURL(string:imageSource), placeholderImage: UIImage(named:"placeholder"))
If you are using Alamofire, you have to manually convert NSData to UIImage. For Alamofire, you can create a NSCache() object to cache your images. The performance of both library should be similar. You can use Alamofire for your server API calls and then use AFNetworking to display images asynchronously.
Sample code using Alamofire for image cache:
let imageCache = NSCache()
if let image = self.imageCache.objectForKey(imageURL) as? UIImage {
cell.imageView.image = image
} else {
// 3
cell.imageView.image = nil
// 4
cell.request = Alamofire.request(.GET, imageURL).validate(contentType: ["image/*"]).responseImage() {
(request, _, image, error) in
if error == nil && image != nil {
// 5
self.imageCache.setObject(image!, forKey: request.URLString)
// 6
if request.URLString == cell.request?.request.URLString {
cell.imageView.image = image
}
} else {
/*
If the cell went off-screen before the image was downloaded, we cancel it and
an NSURLErrorDomain (-999: cancelled) is returned. This is a normal behavior.
*/
}
}
}
You can read this tutorial for more detail about using Alamofire.
Here's an approach using RxAlamofire:
import Alamofire
import RxAlamofire
extension UIImageView {
public func imageFromUrl(urlString: String) {
requestData(.GET, urlString)
.observeOn(MainScheduler.instance)
.subscribeNext { self.image = UIImage(data: $0.1) }
}
}
Actually there are multiple ways to load your image from URL using Alamofire.
and this is a way that i've developed and i'm using it already in one of my Swift projects. This way i'm loading the images Asyncrounsly and display them into UITableView and from performance wise it's fine and there is no blocking while scrolling UITableView because it's care about cashing the images loaded.
First you have to create a GET request using Alamofire to download the image, and you have to use Request+AlamofireImage from AlamofireImage Library so you have to
import AlamofireImage
And then in
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
}
you have to call your image download request like this:
//Download image
// We should perform this in a background thread
Alamofire.request(.GET, deshItem.dish_imageUrl!)
.responseImage { response in
debugPrint(response)
print(response.request)
print(response.response)
debugPrint(response.result)
if let image = response.result.value {
print("image downloaded: \(image)")
// Store the commit date in to our cache
self.ImageCache[dishName!] = image
// Update the cell
dispatch_async(dispatch_get_main_queue(), {
if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) {
let dishImageView:UIImageView = cellToUpdate.viewWithTag(104) as! UIImageView
dishImageView.image = image
}
})
}
}
For Image Caching i've created a dictionary that will contain image for every cell or item(String), So this way of caching images will avoid the locking that appear while scrolling.
Complete Sample code:
import AlamofireImage
class MainFeedTableViewController: UITableViewController, FloatRatingViewDelegate {
var ImageCache = [String:UIImage]()
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MainFeedCellID", forIndexPath: indexPath)
// Configure the cell...
let dishName = deshItem.dish_name
//This is a workaround to cash the image and improve the performance while user scrolling UITableView.
// If this image is already cached, don't re-download
if let dishImage = ImageCache[dishName!] {
let dishImageView:UIImageView = cell.viewWithTag(104) as! UIImageView
dishImageView.image = dishImage
}
else {
//Download image
// We should perform this in a background thread
Alamofire.request(.GET, deshItem.dish_imageUrl!)
.responseImage { response in
debugPrint(response)
print(response.request)
print(response.response)
debugPrint(response.result)
if let image = response.result.value {
print("image downloaded: \(image)")
// Store the commit date in to our cache
self.ImageCache[dishName!] = image
// Update the cell
dispatch_async(dispatch_get_main_queue(), {
if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) {
let dishImageView:UIImageView = cellToUpdate.viewWithTag(104) as! UIImageView
dishImageView.image = image
}
})
}
}
}
return cell
}
You can download image by using responseData(queue:completionHandler:)
SWIFT 4
func downloadImages(imageURL: String) {
Alamofire.request(imageURL, method: .get)
.validate()
.responseData(completionHandler: { (responseData) in
self.yourImageVIew.image = UIImage(data: responseData.data!)
DispatchQueue.main.async {
// Refresh you views
}
})
}
I have developed a small and straightforward CocoaPods library to work with Alamofire and Images.
CocoaPods is a great way of addressing your library dependencies needs. It is easy to install once, and easy to add and update dependencies to your projects.
The library is open source and can be found at https://github.com/gchiacchio/AlamoImage
It has great documentation with examples, so you will be enjoying it in minutes.
I have it working in a couple of project myself, including collection views and scrolling.
it is recommended to use urlsession as urlconnection deprecated in swift 5
public func imageFromUrl(urlString: String){
if let url = URL(string: urlString) {
let request = URLRequest(url: url)
let session = URLSession(configuration: .default)
session.dataTask(with: request,completionHandler: {(data,response,error) in
self.image = UIImage(data: data!)
})
}
}