Kingfisher: Store and retrieve image Kingfisher 5

Created on 16 Feb 2019  路  12Comments  路  Source: onevcat/Kingfisher

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!

All 12 comments

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!

Update

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 url and 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!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

OhKai picture OhKai  路  4Comments

HuseyinVural picture HuseyinVural  路  4Comments

Tarpsvo picture Tarpsvo  路  5Comments

damirstuhec picture damirstuhec  路  3Comments

freak4pc picture freak4pc  路  3Comments