I am trying to cache images using the Cocoapod KingFisher, the code i am using does display the image from the database storage but it does no caching. I am curious as to know why?
The print always says "cache Result none". And i also notice that the images are not cached.
Code for calling the imageDownloader:
DownloadImage(imageId : nextUser.id, cardImage: secondProfilePic)
Code for downloading and caching, also for checking if cached.
func DownloadImage(imageId : String, cardImage : UIImageView){
let imagesStorageRef = Storage.storage().reference().child("profilepic/").child(imageId)
//Get URL For Cache
imagesStorageRef.downloadURL { url, error in
if let error = error {
// Handle any errors
cardImage.image = UIImage(named: "RentOutProfilePic")
print("Error")
} else {
// Get the download URL for '.jpg'
let pathURL = url
print("Sets Image")
cardImage.kf.indicatorType = .activity
cardImage.kf.setImage(with: pathURL,
options: [
.transition(.fade(0.3)),
.cacheOriginalImage
])
}
if let url = url{
let tempUrl:String = url.path
let cache = ImageCache.default
let cached = cache.imageCachedType(forKey: tempUrl)
print("cache Result \(cached)")
}
}
}
Kingfisher is using url.absoluteString as the cache key for an image by default. So in your code, url.path will always return you the result of "not cached".
You are trying to print the cache result as the same time when you set the image. At the first time, your image would be in download progress so you always get .none even you set the key correctly according to 1. In the following invocation of this method with the same id, you should get a cache result either as disk or memory.
I am not sure how did you get the conclusion of "the images are not cached". Kingfisher is doing cache based on url by default. If you have different urls every time (which is returned from your imagesStorageRef) you call the image view setting method, there would be no matching cache and downloading would happen. If this is your case, you can customize to use the imageId as the cache key instead. To do that, you need to specify another cache key. See this wiki section for more.
Related
I have a function called downloadImage. I'm trying to get image data and set an imageView. But it prints "no data".
image link: http://commons.wikimedia.org/wiki/Special:FilePath/Flag%20of%20Montenegro.svg
Here is my code:
func downloadImage() {
let uri = "Special:FilePath/Flag%20of%20Montenegro.svg"
let baseURL = URL(string: "http://commons.wikimedia.org/wiki/")!
let imageURL = URL(string: uri, relativeTo: baseURL)!
if let data = try? Data(contentsOf: imageURL){
if let image = UIImage(data: data){
DispatchQueue.main.async {
self.flagImageView.image = image
}
}
else{
print("image cannot be taken")
}
}else{
print("no data")
}
}
Here is console output:
2022-09-03 21:21:56.426579+0300 CountryBook[5242:197393] nil host used in call to
allowsSpecificHTTPSCertificateForHost
2022-09-03 21:21:56.426913+0300 CountryBook[5242:197393] nil host used in call to allowsAnyHTTPSCertificateForHost:
2022-09-03 21:21:56.427580+0300 CountryBook[5242:197905] NSURLConnection finished with error - code -1002
no data
Note:
I Allowed Arbitrary Loads from info.plist by marking it as YES.
Assuming you did allow the usage of http corectly. There is more going on here:
The link you provided redirects to https://upload.wikimedia.org/wikipedia/commons/6/64/Flag_of_Montenegro.svg. Data(contentsOf: is not for this purpose. It is best suited for loading data from a bundle file url and not complex redirecting or cookies or header..... . Use a proper URLSesssion.
Even if you get your data it will not work this way. UIImage doesn´t support SVG format. See this SO question for more info
Remarks:
Just use https. It´s de facto standard. And your link to wikipedia would support it. Falling back to http should be the last resort while developing and never in production.
this is actually due to the fact that apple no longer allows http urls by default, it wants them to be https
When using a https url, you also shouldn't need to allowArbitraryLoads
From what I suspect though, wikipedia is https, so I think you can just change your url to have https like so:
https://commons.wikimedia.org/wiki/Special:FilePath/Flag%20of%20Montenegro.svg
I currently have a view controller where the user uploads an image and that image is stored in the Firebase Storage, with folders of their user id and in there their uploaded image. I want to fetch that image's url and display it on the view controller. The question I have is, do i need to store that uploaded image's download url into the realtime database ie; Users - > User Id - > Media -> Image Title -> Download URL? Or is there a way for me to refer an image view to Firebase storage and accordingly into the User Id's folder containing all the images and display this? Would appreciate it a lot if someone could help me out. Thank you!
This is my code:
let imageName = (Auth.auth().currentUser?.uid)!+"/\(imageTitle.text!)"
let imageReference = Storage.storage().reference().child(MyKeys.imagesFolder).child(imageName)
imageReference.putData(data, metadata: nil) { (metadata, err) in
if let err = err {
print ("Error")
return
}
imageReference.downloadURL(completion: { (url, err) in
if let err = err {
print ("Error")
return
}
guard let url = url else {
print ("Error")
return
}
let dataReference = Firestore.firestore().collection(MyKeys.imagesCollection).document()
let documentUid = dataReference.documentID
let urlString = url.absoluteString
let data = [
MyKeys.uid:documentUid,
MyKeys.imageUrl: urlString,
MyKeys.imageTitle: self.imageTitle.text!,
] as [String : Any]
dataReference.setData(data) { (err) in
if let err = err {
print ("Error")
return
}
}
})
}
There are two ways to read the data from Storage:
Through the Firebase SDK by calling getData or write on a reference, as shown in the documentation on downloading data.
Through a download URL for the reference, which provides public read-only access.
To call getData, write or to get a download URL, you need to have a reference to the file as shown in creating a reference.
Since you store the files under the UID of the user, you can always create a reference to any for for a user if you known their UID. So you can always perform one of the two methods above to read the data for the file, even if you didn't store the download URL.
That said, it is fairly common to store the download URL in a database, as it means you can then treat it like any other image URL. By doing so the rest of your code won't have to know anything about Cloud Storage.
So: it can work without storing the download URL in the database, but it's also fine (and common) if you do store the download URL. The choice is really up to you.
I am saving a list of file names/paths so I can load the image at a later time to upload it.
When the user selects the images from the camera roll, I get back this
file:///Users/admin/Library/Developer/CoreSimulator/Devices/B31CE61D-FB46-41F0-B254-B66B9335E1E4/data/Media/DCIM/100APPLE/IMG_0005.JPG
But when I try to load up the image,
if let image = UIImage(named: filepath) {
imageView.image = image
}
It doesn't load.
How do I load an image from a filepath?
The code I use to get the file path
func getURL(ofPhotoWith mPhasset: PHAsset, completionHandler : #escaping ((_ responseURL : URL?) -> Void)) {
let options = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = {(adjustmeta: PHAdjustmentData) -> Bool in
return true
}
mPhasset.requestContentEditingInput(with: options, completionHandler: { (contentEditingInput, info) in
completionHandler(contentEditingInput!.fullSizeImageURL)
})
}
func add(images: [PHAsset]) {
for image in images {
getURL(ofPhotoWith: image) { (imgURL) in
if let imgURL = imgURL {
print ("ImageURL: \(imgURL.absoluteString)")
}
}
}
}
I am saving a list of file names/paths so I can load the image at a later time to upload it.
PHContentEditingInput is the wrong tool for that job. As the names of that class and the functions you're using to get one suggest, it's for content editing — tasks like applying a filter to an asset in the library.
When PHContentEditingInput gives you a file URL, it's granting you temporary access to that file. PhotoKit makes no guarantee that the asset in question will always be backed by a file at that URL, and even if it is, PhotoKit revokes temporary access to that URL when the owning PHContentEditingInput is deallocated.
A user's Photos library isn't a directory full of image files — it's a database, where each asset can have data resources stored in one or more files, which might or might not even be in local storage at all times. If you want to upload assets to an external service and preserve all the original data, you need an API that's meant for getting data resources. PhotoKit gives you two choices for that:
If you want just some image representation of the current state of the asset, use PHImageManager. This downloads and/or generates image data ready for you to save as a file, incorporating whatever edits the user has already applied to the asset:
let options = PHImageRequestOptions()
options.deliveryMode = .highQualityFormat
options.isNetworkAccessAllowed = true
PHImageManager.default().requestImageData(for: myAsset, options: options) { data, uti, orientation, info in
// save `data` to file / upload to web service
// use `uti` to find out what file type it is
}
If you want the original image data resources — that is, enough data that you could back up and restore the asset, including features like in-progress edits, Live Photo modes, and RAW format image data — use PHAssetResource and PHAssetResourceManager:
let resources = PHAssetResource.resources(for: myAsset)
let options = PHAssetResourceRequestOptions()
options.isNetworkAccessAllowed = true
for resource in resources {
let outputURL = myOutputDirectory.appendingPathComponent(resource.originalFilename)
PHAssetResourceManager.default().writeData(for: resource, to: outputURL, options: options) { error in
// handle error if not nil
}
}
I am saving a list of file names/paths so I can load the image at a later time to upload it when the user selects the images from the camera roll
Don't. The thing to save so that you can retrieve something from the camera roll at a later time is the PHAsset's localIdentifier. You can use that to get the same asset again later, and now you can ask for the associated image.
I need to cache images on disk but to limit them say to 20 images. I am trying Nuke library. When I run
Nuke.loadImage(with: url, options: options, into: imageView)
image is cached as long as I am inside my View Controller. When I leave the views, next time images are being fetched again. So how do I make Nuke (or other lib) save those images for specific image count or time.
Before trying Nuke I was just saving images to Documents folder of the app every time I fetch image. I am sure there is a better way.
Update: I was able to do it with Kingfisher.
func getFromCache(id: String) {
ImageCache.default.retrieveImage(forKey: id, options: nil) {
image, cacheType in
if let image = image {
self.galleryImageView.image = image
} else {
print("Not exist in cache.")
self.loadImage()
}
}
}
private func loadImage() {
ImageDownloader.default.downloadImage(with: url, options: [], progressBlock: nil) {
(image, error, url, data) in
if let image = image {
self.galleryImageView.image = image
ImageCache.default.store(image, forKey: id, toDisk: true)
}
}
}
If I understand correctly, first retrieveImage fetches images from disk cache. Later from memory cache. And it frees them only when memory warning received. I hope it does. God help us.
There are several advanced options for image caching. You can get a direct access to Memory Cache (default Nuke's ImagePipeline has two cache layers):
// Configure cache
ImageCache.shared.costLimit = 1024 * 1024 * 100 // Size in MB
ImageCache.shared.countLimit = 20 // You may use this option for your needs
ImageCache.shared.ttl = 90 // Invalidate image after 90 sec
// Read and write images
let request = ImageRequest(url: url)
ImageCache.shared[request] = image
let image = ImageCache.shared[request]
// Clear cache
ImageCache.shared.removeAll()
Also, use ImagePreheater class. Preheating (a.k.a. prefetching) means loading images ahead of time in anticipation of their use. Nuke lib provides an ImagePreheater class that does just that:
let preheater = ImagePreheater(pipeline: ImagePipeline.shared)
let requests = urls.map {
var request = ImageRequest(url: $0)
request.priority = .low
return request
}
// User enters the screen:
preheater.startPreheating(for: requests)
// User leaves the screen:
preheater.stopPreheating(for: requests)
You can use Nuke lib in combination with Preheat library which automates preheating of content in UICollectionView and UITableView. On iOS 10.0 and above you might want to use new prefetching APIs provided by iOS instead.
I'm trying to get my head around Firebase Storage.
I've seen online and tried two methods of getting my images.
What is the difference between these two? (both work).
So after I get the photoUrl from my Firebase Database:
1.
if let data = NSData(contentsOfURL: NSURL(string:photoUrl)!)
{
let myImage = UIImage(data: data)!
MyImageCache.sharedCache.setObject(myImage, forKey: self.key)
//etc
}
2.
self.storage.referenceForURL(photoUrl).dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if (error != nil)
{
print(error)
}
else
{
let myImage = UIImage(data: data!)
MyImageCache.sharedCache.setObject(myImage!, forKey: self.key)
//etc
}
}
Regarding the first method, you shouldn't be using that for network calls. From the docs:
Do not use this synchronous method to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated. Instead, for non-file URLs, consider using the dataTaskWithURL:completionHandler: method of the NSURLSession class. See URL Session Programming Guide for details.
The second method is baked into the firebase framework and provides you with convenience methods for downloading an image, i.e. it gives you the option to specify images size. This is probably optimised for getting images and is in most scenarios the preferred method.