Hi there,
I updated Kingfisher to 5 and the images not store anymore.
That is my code:
ImageCache.default.retrieveImage(forKey: picture, options: nil) { result in
switch result {
case .success(let value):
if value.image == nil { //always shows "none"
let imgRef = mainVariables.storageBucketImages.reference(withPath: "/images/users/" + picture)
// Fetch the download URL
imgRef.downloadURL { url, error in
if error != nil {
self.iv_avatar.image = UIImage (named: "avatar")
self.activityIndicator.stopAnimating()
}
else {
self.iv_picture.kf.setImage(with: url) { result in
switch result {
case .success(let valueImg):
ImageCache.default.store(valueImg.image, forKey: picture)
if CGFloat((valueImg.image.size.width)) > CGFloat((valueImg.image.size.height)) {
self.iv_picture.image = nil
self.iv_avatar.image = self.resizeImage(image: valueImg.image, targetSize: CGSize.init(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
}
else {
self.iv_avatar.image = nil
self.iv_avatar.backgroundColor = UIColor.clear
self.iv_picture.image = self.resizeImage(image: valueImg.image, targetSize: CGSize.init(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
}
case .failure( _):
if self.profileUser != nil {
self.iv_avatar.image = UIImage (named: "avatar")
}
else {
self.iv_avatar.image = UIImage (named: "avatar")
}
}
self.activityIndicator.stopAnimating()
}
}
}
}
else {
if CGFloat((value.image?.size.width)!) > CGFloat((value.image?.size.height)!) {
self.iv_picture.image = nil
self.iv_avatar.image = self.resizeImage(image: value.image!, targetSize: CGSize.init(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
}
else {
self.iv_avatar.image = nil
self.iv_avatar.backgroundColor = UIColor.clear
self.iv_picture.image = self.resizeImage(image: value.image!, targetSize: CGSize.init(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
}
self.activityIndicator.stopAnimating()
}
case .failure(let error):
print("Error: \(error)")
}
}
Is there something that I am missing?
Thanks a lot!
Your code looks fine to me.
Is there any error happens during downloading or caching? For example, you can try to print the result in these places:
self.iv_picture.kf.setImage(with: url) { result in
print(result)
// ...
ImageCache.default.store(valueImg.image, forKey: picture) {result in
print(result.diskCacheResult)
}
}
I also am always getting nil during ImageCache.default.retrieveImage().
Essentially if the image is cached then use the cached version. If not, go and create a thumbnail image from a video URL. I can confirm that the thumbnail and image code is working correctly because the thumbnail that is created on the fly is showing up, however when switching on value the result is always .none leading to the function -> self.setThumbnail(urlString: urlString, width: width, height: height)
Here is my code (Swift 4):
let urlString = URL(string: "someString")!
let cache = ImageCache.default
let width = self.contentView.bounds.width
let height = self.contentView.bounds.height
if cache.isCached(forKey: urlString) {
cache.retrieveImage(forKey: "cacheKey") { result in
switch result {
case .success(let value):
switch value {
case .none:
self.setThumbnail(urlString: urlString, width: width, height: height)
case .disk:
DispatchQueue.main.async { self.imageView.image = value.image }
case .memory:
DispatchQueue.main.async { self.imageView.image = value.image }
}
case .failure(let error):
print(error)
}
}
} else {
setThumbnail(urlString: urlString, width: width, height: height)
}
private func setThumbnail(urlString: String, width: CGFloat, height: CGFloat){
DispatchQueue.global(qos: .userInitiated).async {
if let image = self.createThumbnailOfVideoFromRemoteUrl(url: urlString, width: width, height: height) {
ImageCache.default.store(image, forKey: urlString)
DispatchQueue.main.async {
self.imageView.image = image
}
}
}
}
func createThumbnailOfVideoFromRemoteUrl(url: String, width: CGFloat, height: CGFloat) -> UIImage? {
guard let vidURL = URL(string: url) else { return nil }
let asset = AVAsset(url: vidURL)
let assetImgGenerate = AVAssetImageGenerator(asset: asset)
assetImgGenerate.appliesPreferredTrackTransform = true
//Can set this to improve performance if target size is known before hand
//assetImgGenerate.maximumSize = CGSize(width: width, height: height)
let time = CMTime(seconds: 1, preferredTimescale: 10)
do {
let img = try assetImgGenerate.copyCGImage(at: time, actualTime: nil)
let thumbnail = UIImage(cgImage: img)
return thumbnail
} catch {
print(error.localizedDescription)
return nil
}
}
Let me know if you need any other information. Thanks in advance!
I changed the store function to:
ImageCache.default.store(image, forKey: urlString, completionHandler: { (result) in
print(result)
})
and this is the result in the debugger
CacheStoreResult(memoryCacheResult: Result.success(()), diskCacheResult: Result.success(()))
Hi, @patrikdevlin
I am not sure is it only in your code snippet here. But you are using urlString as the cache key, while using "cacheKey" to retrieve, so it might be a problem.
You said you can go to the switch statement, so cache.isCached(forKey: urlString) succeeded. It should be in either memory or disk cache.
Can you confirm that you are trying to get and set the cache with the same key?
In fact we have unit test for both memory cache and disk cache, it is not likely it fails.
Hi, @onevcat
I appreciate the timely response! Sometimes you need the extra eye... That did the job! Sorry to use up your time on such a trivial fix.
I have another quick question if you have second. Is there any way to use imageView.kf.setImage() with a UIImage() i.e the one being returning from the video thumbnail? Or is it just for remote URLs?
Best,
Pat
@patrikdevlin
Yes, create your own ImageDataProvider like this:
struct VideoThumbnailImageProvider: ImageDataProvider {
enum ProviderError: Error {
case convertingFailed(CGImage)
}
let url: URL
let size: CGSize
var cacheKey: String { return "\(url.absoluteString)_\(size)" }
func data(handler: @escaping (Result<Data, Error>) -> Void) {
DispatchQueue.global(qos: .userInitiated).async {
let asset = AVAsset(url: self.url)
let assetImgGenerate = AVAssetImageGenerator(asset: asset)
assetImgGenerate.appliesPreferredTrackTransform = true
assetImgGenerate.maximumSize = self.size
let time = CMTime(seconds: 1, preferredTimescale: 10)
do {
let img = try assetImgGenerate.copyCGImage(at: time, actualTime: nil)
if let data = UIImage(cgImage: img).jpegData(compressionQuality: 0.8) {
handler(.success(data))
} else {
handler(.failure(ProviderError.convertingFailed(img)))
}
} catch {
handler(.failure(error))
}
}
}
}
And then you can set the image:
let p = VideoThumbnailImageProvider(
url: URL(string: "abc")!,
size: CGSize(width: 100, height: 100))
imageView.kf.setImage(with: p)
Be careful that size is also a part of the cacheKey in the code snippet above, so if the size is different, the thumbnail will be fetched again and stored.
You can read the wiki on ImageDataProvider to know more about it.
(I guess it is what you need. If you only need a raw image setting, check the RawImageDataProvider instead.)
@onevcat I have the same question, when i retrived one image, it always return 'Kingfisher.ImageCacheResult.none'. Using 4.* version, my code work.
KingfisherManager.shared.cache.retrieveImage(forKey: url, options: nil) { (result) in
dispathAsynOnMain {
switch result {
case .success(let value):
completion(value.image, nil)
case .failure(let error):
completion(nil, error)
}
}
}
@elijahdou What is the url and how did you download and set the image to cache?
@elijahdou What is the
urland how did you download and set the image to cache?
@onevcat url is a common image url like http://img.*/*.jpeg-*, the real image format is webP. The download code as follow:
imageView.kf.setImage(with: ImageResource(downloadURL: url), placeholder: placeholder, options: [.downloadPriority(1.0), .transition(ImageTransition.none), .keepCurrentImageWhileLoading], progressBlock: nil) { (result) in
}
The imageView shows image fine, and i always fail to retrieve image with Result.success(Kingfisher.ImageCacheResult.none)
WebP is not supported built-in in Kingfisher now. You may need to check this and try a customized image processor.
WebP is not supported built-in in Kingfisher now. You may need to check this and try a customized image processor.
@onevcat I have set webP processor code in didFinshLaunch as follow
KingfisherManager.shared.defaultOptions = [.processor(WebPProcessor.default), .cacheSerializer(WebPSerializer.default)]
Now, I can retrivie cached image with code as follow
KingfisherManager.shared.retrieveImage(with: ImageResource(downloadURL: URL(string: url)!), options: [.onlyFromCache]) { (result) in
dispathAsynOnMain {
switch result {
case .success(let value):
completion(value.image, nil)
case .failure(let error):
completion(nil, error)
}
}
}
I don't konw the difference KingfisherManager.shared.retrieveImage and KingfisherManager.shared.cache.retrieveImage in 5.*, but the latter did work in 4.*
The retrieve method in KingfisherManager will try to get the image from cache first, if not found, it will download and process it.
However, the method for cache will just try to get the image from cache.
Both methods are well documented and please read them.
The discussion is now not related to its original context, so I think it's the time to close it. @elijahdou Please open a new one and follow the issue template to add some more detail if you still face a problem. Thanks!