Swift Haneke: cache doesn't resolve - swift

I'm trying to pre-load some images with the following code:
let thumbnailUrl = NSURL(string: urlString)
let fetcher = NetworkFetcher<UIImage>(URL: thumbnailUrl)
Shared.imageCache.fetch(fetcher) {
println("Finished")
}
But afterwards when I try to set it to the imageview, it downloads it again from the network instead of reading it from the cache. This is the code:
self.imageView.hnk_setImageFromURL(
NSURL(string: urlString)
success: { thumbnail in
println("Finished setting image")
}
)
Is this a bug or maybe I missunderstood the usage of imageCache.fetch()?
PD: I put breakpoints in the whole code, and I can guarantee the key for the cache (in this case, the url) is exactly the same, so I have no clue why the cache isn't resolving when used with .hnk_setImageFromURL()

I was stuck on this for 3 days. I hope this answer might help someone else in the future.
The issue was that I didn't specify a Format Name to the fetcher, while the UIImageView.hnk_setImageFromURL() does. The way to fix it is:
let thumbnailUrl = NSURL(string: urlString)
let fetcher = NetworkFetcher<UIImage>(URL: thumbnailUrl)
let cache = Shared.imageCache
let format = self.imageView.hnk_format
cache.fetch(fetcher, formatName: format.name) {
println("Finished")
}

Related

Download image from URL and save it to Core Data swift

There is I am trying to download image from URL and save it to variable of model. But it's not setting the image. Inside setImage I saw that it's downloading some data. URL's are checked and working. But when I check it for nil it's showing "empty input", "empty output".
let inputImage = UIImageView()
let outputImage = UIImageView()
DispatchQueue.main.async {
let inputURL = URL(string: "someURL")
let outputURL = URL(string: "someURL")
inputImage.kf.setImage(with: inputURL)
outputImage.kf.setImage(with: outputURL)
}
let coreDataModel = CoreDataModel()
if let inputImageData = inputImage.image?.pngData()
{ cloudAnalysisModel.input_image = inputImageData }
else
{ print("empty input") }
if let outputImageData = outputImage.image?.pngData()
{ coreDataModel.output_image = outputImageData }
else
{ print("empty output") }
I think you have not understood how asynchronous programming works. The block or completionHandler (the one that starts with { and ends with }) gets executed after url is called and response is obtained
What I mean to say here is , that block maybe executed at any time and the code after that is executed right away i.e the code from let coreDataModel onwards
So it is obvious that both the inputImageData and outputImageData are going to be null
Now coming to the solutions:
What you could do is:
Shift all the code inside the callback block instead of keeping it outside and show the user some progressbar or UI
Use DispatchGroup which keeps track of the DispatchQueue More info here

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"))

Swift - Check if the file download from a URL is completed

I am downloading mp4 clips from a M3U8 manifest which can have around 700 clips. Everything works fine to download them but what would be the best to check individual downloads are finished? When all the clips are downloaded, I merge them into one but I need to know when all my clips have been downloaded first.
This is the code snippet I use to download the video clip.
func download(video: String){
DispatchQueue.global(qos: .background).async {
if let url = URL(string: "http://SERVER/storage/sessions/SESSIONID/mp4_segments/\(video)"),
let urlData = NSData(contentsOf: url) {
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0];
let fileName = video
let filePath = "\(documentsPath)/SegmentVideos/\(fileName)"
urlData.write(toFile: filePath, atomically: true)
}
}
}
This is the code snippet that reads the M3U8 file and splits it so I can grab the video clip's name.
func checkM3U8forClips(){
guard let url = url else {return}
do {
let contents = try String(contentsOf: url)
let splitContent = contents.components(separatedBy: "\n")
for split in splitContent {
if split.hasSuffix("mp4") {
download(video: split)
}
}
} catch {
print("error with mp4 segments: \(error.localizedDescription)")
}
}
One reason you are in a quandary is that this code is wrong:
if let url = URL(string: "http://SERVER/storage/sessions/SESSIONID/mp4_segments/\(video)"),
let urlData = NSData(contentsOf: url) {
You must never use NSData(contentsOf:) to do networking. If you want to network, then network: use URLSession and a proper data task or download task. Now you get the callbacks you need; if you do it in the full form you get a full set of delegate callbacks that tell you exactly when a download has succeeded and completed (or failed).
As for your overall question, i.e. how can I know when multiple asynchronous operations have all finished, that is what things like DispatchGroup, or operation dependencies, or the new Combine framework are for.

Swift Image from URL

I am trying to display an image from a URL in swift using the code below but nothing appears.
let url = NSURL(string: "https://www.psychicliving.co.uk/images/Michael.jpg")
if let data = NSData(contentsOf: url as! URL) {
cell.imgCarNane.image = UIImage(data: data as Data)
}
The odd thing is that if I substitute the image for a jpeg hosted on a different site like:
https://www.thawte.com/assets/hp/images/promo_myths.jpg
it displays fine.
Is there something about the image I am trying to use that would be causing the issue?
if someone could take a look I would be very greatful.
Thanks!
I don't know why since the website you're retrieving the image from is using https (and seems secured), but you have to add the website (or allow arbitrary loads) in your plist.
Create an entry App Transport Security Settings https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html
add NSAppTransportSecurity in info.plist
NSAllowsArbitraryLoads= YES
let url = URL(string: "https://www.psychicliving.co.uk/images/Michael.jpg")
let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
imageViewTest.image = UIImage(data: data!)
}

How to clear AlamofireImage setImageWithURL cache

I am using AlamofireImage in my project quite a lot and I use
let URL = NSURL(string: "https://cdn.domain.com/profile/image.jpg")!
imageView.af_setImageWithURL(URL)
to fetch an image from my CDN. I have done some tests but correct me if I am wrong, this seems to store the downloaded image in to a cache. My tests included downloading a 5mb image. The first time it took about 20 seconds, the second time was instant.
The thing I would like to know is how can I clear the cache for a specific URL/image and re-download the image?
Say for example I update a users profile pic. The image name/URL will be exactly the same but I know the image has changes as the user selected a new image from their library or camera. I know the image has been uploaded successfully to the CDN as I can see the new image in the folder directly on the CDN.
You need to remove the image from the in-memory cache as well as the on-disk cache. You can do this as follows:
func clearImageFromCache() {
let URL = NSURL(string: "https://cdn.domain.com/profile/image.jpg")!
let URLRequest = NSURLRequest(URL: URL)
let imageDownloader = UIImageView.af_sharedImageDownloader
// Clear the URLRequest from the in-memory cache
imageDownloader.imageCache?.removeImageForRequest(URLRequest, withAdditionalIdentifier: nil)
// Clear the URLRequest from the on-disk cache
imageDownloader.sessionManager.session.configuration.URLCache?.removeCachedResponseForRequest(URLRequest)
}
Currently, the URLCache can only be cleared in this manner on the master branch. I just pushed f35e4748 which allows access to the underlying sessionManager in the ImageDownloader. This is not yet available in an actual release yet, but should be here sometime this week.
Swift3 and AlamofireImage 3.x
It appears removeImage does all that is needed.
// Clear what is in the cache, this will force a refresh to ensure fresh image is loaded next time
let urlRequest = Foundation.URLRequest(url: validUrl)
let imageDownloader = UIImageView.af_sharedImageDownloader
if let imageCache2 = imageDownloader.imageCache {
_ = imageCache2.removeImage(for: urlRequest, withIdentifier: nil)
}
Hope this might help you:
let URL = NSURL(string: "https://cdn.domain.com/profile/image.jpg")!
let imageDownloader = UIImageView.af_sharedImageDownloader
let imageCache = imageDownloader.imageCache
// Setting CachePolicy as reloadIgnoringLocalCacheData so that it won't use URL Cache next time when it is hitting same URL
let urlRequest = URLRequest(url: URL, cachePolicy: URLRequest.CachePolicy.reloadIgnoringLocalCacheData)
// Clear the Image from the in-memory cache
let _ = imageCache?.removeImage(for: urlRequest, withIdentifier: nil)
imageView.af_setImage(withURLRequest: urlRequest, placeholderImage: UIImage(named: "placeholder"), completion: { (response) in
self.imageView.image = response.result.value
})
Swift 5:
func clearImageCache(forUrl urlString: String) {
guard let url = URL(string: urlString) else {
return
}
let imageDownloader = UIImageView.af_sharedImageDownloader
let urlRequest = URLRequest(url: url)
// Clear the URLRequest from the in-memory cache
_ = imageDownloader.imageCache?.removeImage(for: urlRequest, withIdentifier: nil)
// Clear the URLRequest from the on-disk cache
imageDownloader.session.sessionConfiguration.urlCache?.removeCachedResponse(for: urlRequest)
}
My solution for the caching issue:
func af_setImageIgnoreCache(string: String?) {
guard let url = string, let nsurl = URL(string: url) else { return }
let urlRequest = URLRequest(url: nsurl, cachePolicy: .reloadIgnoringCacheData)
let imageDownloader = ImageDownloader.default
if let imageCache = imageDownloader.imageCache as? AutoPurgingImageCache, let urlCache = imageDownloader.sessionManager.session.configuration.urlCache {
_ = imageCache.removeImages(matching: urlRequest)
urlCache.removeCachedResponse(for: urlRequest)
}
af_setImage(withURLRequest: urlRequest)
}
The response left by #cnoon was correct. The reason it isn't working for so many of you was because you were probably using some sort of filter and passing nil as the withIdentifier parameter.
I was using a circular filter in this scenario:
// Clearing the cache didn't work like this
imageDownloader.imageCache?.removeImage(for: urlRequest, withIdentifier: nil)
// Worked when adding a CircleFilter().identifier() as `withIdentifier` as such:
imageDownloader.imageCache?.removeImage(for: urlRequest, withIdentifier: CircleFilter().identifier)