-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Cheat Sheet
This documentation will describe some most common usage of Kingfisher. The code snippet is based on iOS. However, the similar code should also work for other platforms like macOS or tvOS, by replacing the corresponding class (such as UIImage
to NSImage
, etc).
This guide covers the most useful part of Kingfisher components. If you want to know the detail of them, please also check the full API Reference.
The view extension based APIs (for UIImageView
, NSImageView
, UIButton
and NSButton
) should be your first choice whenever possible. It keeps your code simple and elegant.
let url = URL(string: "https://example.com/image.jpg")
imageView.kf.setImage(with: url)
This simple code:
- Checks whether an image is cached under the key
url.absoluteString
. - If an image was found in the cache (either in memory or disk), sets it to
imageView.image
. - If not, creates a request and download it from
url
. - Converts the downloaded data to a
UIImage
object. - Caches the image to memory cache, and store the data to the disk cache.
- Sets the
imageView.image
to display it.
Later, when you call setImage
with the same url
again, only the first two steps will be performed, unless the cache is purged.
let image = UIImage(named: "default_profile_icon")
imageView.kf.setImage(with: url, placeholder: image)
The image
will show in the imageView
while downloading from url
.
You could also use a customized
UIView
orNSView
as placeholder, by conforming it toPlaceholder
:class MyView: UIView { /* Your implementation of view */ } extension MyView: Placeholder { /* Just leave it empty */} imageView.kf.setImage(with: url, placeholder: MyView())The
MyView
instance will be added to / removed from theimageView
as needed.
imageView.kf.indicatorType = .activity
imageView.kf.setImage(with: url)
Show a UIActivityIndicatorView
in center of image view while downloading.
imageView.kf.setImage(with: url, options: [.transition(.fade(0.2))])
imageView.kf.setImage(with: url) { result in
// `result` is either a `.success(RetrieveImageResult)` or a `.failure(KingfisherError)`
switch result {
case .success(let value):
// The image was set to image view:
print(value.image)
// From where the image was retrieved:
// - .none - Just downloaded.
// - .memory - Got from memory cache.
// - .disk - Got from disk cache.
print(value.cacheType)
// The source object which contains information like `url`.
print(value.source)
case .failure(let error):
print(error) // The error happens
}
}
let processor = RoundCornerImageProcessor(cornerRadius: 20)
imageView.kf.setImage(with: url, options: [.processor(processor)])
There are also a bunch of built-in processors in Kingfisher. See the Processor section below.
Sometimes, you just want to get the image with Kingfisher instead of setting it to an image view. Use KingfisherManager
for it:
KingfisherManager.shared.retrieveImage(with: url) { result in
// Do something with `result`
}
Kingfisher is using a hybrid ImageCache
to manage the cached images, It consists of a memory storage and a disk storage, and provides high-level APIs to manipulate the cache system. If not specified, the ImageCache.default
instance will be used across in Kingfisher.
By default, URL will be used to create a string for the cache key. For network URLs, the absoluteString
will be used. In any case, you change the key by creating an ImageResource
with your own key.
let resource = ImageResource(downloadURL: url, cacheKey: "my_cache_key")
imageView.kf.setImage(with: resource)
Kingfisher will use the cacheKey
to search images in cache later. Use a different key for a different image.
let cache = ImageCache.default
let cached = cache.isCached(forKey: cacheKey)
// To know where the cached image is:
let cacheType = cache.imageCachedType(forKey: cacheKey)
// `.memory`, `.disk` or `.none`.
If you used a processor when you retrieve the image, the processed image will be stored in cache. In this case, also pass the processor identifier:
let processor = RoundCornerImageProcessor(cornerRadius: 20)
imageView.kf.setImage(with: url, options: [.processor(processor)])
// Later
cache.isCached(forKey: cacheKey, processorIdentifier: processor.identifier)
cache.retrieveImage(forKey: "cacheKey") { result in
switch result {
case .success(let value):
print(value.cacheType)
// If the `cacheType is `.none`, `image` will be `nil`.
print(value.image)
case .failure(let error):
print(error)
}
}
For memory storage, you can set its totalCostLimit
and countLimit
:
// Limit memory cache size to 300 MB.
cache.memoryStorage.config.totalCostLimit = 300 * 1024 * 1024
// Limit memory cache to hold 150 images at most.
cache.memoryStorage.config.countLimit = 150
By default, the totalCostLimit
of memory cache is 25% of your total memory in the device, and there is no limit on image count.
For disk storage, you can set sizeLimit
for space on the file system.
// Limit disk cache size to 1 GB.
cache.diskStorage.config.sizeLimit = = 1000 * 1024 * 1024
Both memory storage and disk storage have default expiration setting. Images in memory storage will expire after 5 minutes from last accessed, while it is a week for images in disk storage. You can change this value by:
// Memory image expires after 10 minutes.
cache.memoryStorage.config.expiration = .seconds(600)
// Disk image never expires.
cache.diskStorage.config.expiration = .never
If you want to override this expiration for a certain image when caching it, pass in with an option:
// This image will never expire in memory cache.
imageView.kf.setImage(with: url, options: [.memoryCacheExpiration(.never)])
The expired memory cache will be purged with a duration of 2 minutes. If you want it happens more frequently:
// Check memory clean up every 30 seconds.
cache.memoryStorage.config.cleanInterval = 30
By default, view extension methods and KingfisherManager
will store the retrieved image to cache automatically. But you can also store an image to cache yourself:
let image: UIImage = //...
cache.store(image, forKey: cacheKey)
If you have the original data of that image, also pass it to ImageCache
, it helps Kingfisher to determine in which format the image should be stored:
let data: Data = //...
let image: UIImage = //...
cache.store(image, original: data, forKey: cacheKey)
Kingfisher manages its cache automatically. But you still can manually remove a certain image from cache:
cache.default.removeImage(forKey: cacheKey)
Or, with more control:
cache.removeImage(
forKey: cacheKey,
processorIdentifier: processor.identifier,
fromMemory: false,
fromDisk: true)
{
print("Removed!")
}
// Remove all.
cache.clearMemoryCache()
cache.clearDiskCache { print("Done") }
// Remove only expired.
cache.cleanExpiredMemoryCache()
cache.cleanExpiredDiskCache { print("Done") }
ImageCache.default.calculateDiskStorageSize { result in
switch result {
case .success(let size):
print("Disk cache size: \(Double(size) / 1024 / 1024) MB")
case .failure(let error):
print(error)
}
}
// The `name` parameter is used to identify the disk cache bound to the `ImageCache`.
let cache = ImageCache(name: "my-own-cache")
imageView.kf.setImage(with: url, options: [.targetCache(cache)])
ImageDownloader
wraps a URLSession
for downloading an image from the Internet. Similar to ImageCache
, there is an ImageDownloader.default
for downloading tasks.
Usually, you may use Kingfisher's view extension methods or KingfisherManager
to retrieve an image. They will try to search in the cache first to prevent unnecessary download task. In some cases, if you only want to download a target image without caching it:
let downloader = ImageDownloader.default
downloader.downloadImage(with: url) { result in
switch result {
case .success(let value):
print(value.image)
case .failure(let error):
print(error)
}
}
When you have permission control for your image resource, you can modify the request by using a .requestModifier
:
let modifier = AnyModifier { request in
var r = request
r.setValue("abc", forHTTPHeaderField: "Access-Token")
return r
}
downloader.downloadImage(with: url, options: [.requestModifier(modifier)]) {
result in
// ...
}
// This option also works for view extension methods.
imageView.kf.setImage(with: url, options: [.requestModifier(modifier)])
If you need to perform some asynchronous operation before modifying the request, create a type and conform to AsyncImageDownloadRequestModifier
:
class AsyncModifier: AsyncImageDownloadRequestModifier {
var onDownloadTaskStarted: ((DownloadTask?) -> Void)?
func modified(for request: URLRequest, reportModified: @escaping (URLRequest?) -> Void) {
var r = request
someAsyncOperation { result in
r.someProperty = result.property
reportModified(r)
}
}
}
Similar as above, you can use the .requestModifier
to use this modifier. In this case, the setImage(with:options:)
or ImageDownloader.downloadImage(with:options:)
method will not return a DownloadTask
anymore (since it does not start the download task immediately). Instead, you observe one from the onDownloadTaskStarted
callback if you need a reference to the task:
let modifier = AsyncModifier()
modifier.onDownloadTaskStarted = { task in
if let task = task {
print("A download task started: \(task)")
}
}
let nilTask = imageView.kf.setImage(with: url, options: [.requestModifier(modifier)])
If the downloading started, a DownloadTask
will be returned. You can use it to cancel an on-going download task:
let task = downloader.downloadImage(with: url) { result in
// ...
case .failure(let error):
print(error.isTaskCancelled) // true
}
}
// After for a while, before download task finishes.
task?.cancel()
If the task already finished when you call task?.cancel()
, nothing will happen.
Similar, the view extension methods also return DownloadTask
. You can store and cancel it:
let task = imageView.kf.set(with: url)
task?.cancel()
Or, you can call cancelDownloadTask
on the image view to cancel the current downloading task:
let task1 = imageView.kf.set(with: url1)
let task2 = imageView.kf.set(with: url2)
imageView.kf.cancelDownloadTask()
// `task2` will be cancelled, but `task1` is still running.
// However, the downloaded image for `task1` will not be set because the image view expects a result from `url2`.
The ImageDownloader
uses a default behavior (.performDefaultHandling
) when receives a challenge from server. If you need to provide your own credentials, setup an authenticationChallengeResponder
:
// In ViewController
ImageDownloader.default.authenticationChallengeResponder = self
extension ViewController: AuthenticationChallengeResponsable {
var disposition: URLSession.AuthChallengeDisposition { /* */ }
let credential: URLCredential? { /* */ }
func downloader(
_ downloader: ImageDownloader,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
// Provide your `AuthChallengeDisposition` and `URLCredential`
completionHandler(disposition, credential)
}
func downloader(
_ downloader: ImageDownloader,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
// Provide your `AuthChallengeDisposition` and `URLCredential`
completionHandler(disposition, credential)
}
}
By default, the download timeout for a request is 15 seconds. To set it for the downloader:
// Set timeout to 1 minute.
downloader.downloadTimeout = 60
To define a timeout for a certain request, use a .requestModifier
:
let modifier = AnyModifier { request in
var r = request
r.timeoutInterval = 60
return r
}
downloader.downloadImage(with: url, options: [.requestModifier(modifier)])
ImageProcessor
transforms an image (or data) to another image. You can provide a processor to ImageDownloader
to apply it to the downloaded data. Then processed image will be sent to the image view and the cache.
// Just without anything
imageView.kf.setImage(with: url)
// It equals to
imageView.kf.setImage(with: url, options: [.processor(DefaultImageProcessor.default)])
DefaultImageProcessor
converts downloaded data to a corresponded image object. PNG, JPEG, and GIF are supported.
// Round corner
let processor = RoundCornerImageProcessor(cornerRadius: 20)
// Downsampling
let processor = DownsamplingImageProcessor(size: CGSize(width: 100, height: 100))
// Cropping
let processor = CroppingImageProcessor(size: CGSize(width: 100, height: 100), anchor: CGPoint(x: 0.5, y: 0.5))
// Blur
let processor = BlurImageProcessor(blurRadius: 5.0)
// Overlay with a color & fraction
let processor = OverlayImageProcessor(overlay: .red, fraction: 0.7)
// Tint with a color
let processor = TintImageProcessor(tint: .blue)
// Adjust color
let processor = ColorControlsProcessor(brightness: 1.0, contrast: 0.7, saturation: 1.1, inputEV: 0.7)
// Black & White
let processor = BlackWhiteProcessor()
// Blend (iOS)
let processor = BlendImageProcessor(blendMode: .darken, alpha: 1.0, backgroundColor: .lightGray)
// Compositing
let processor = CompositingImageProcessor(compositingOperation: .darken, alpha: 1.0, backgroundColor: .lightGray)
// Use the process in view extension methods.
imageView.kf.setImage(with: url, options: [.processor(processor)])
// First blur the image, then make it round cornered.
let processor = BlurImageProcessor(blurRadius: 4) |> RoundCornerImageProcessor(cornerRadius: 20)
imageView.kf.setImage(with: url, options: [.processor(processor)])
Make a type conforming to ImageProcessor
by implementing identifier
and process
:
struct MyProcessor: ImageProcessor {
// `identifier` should be the same for processors with the same properties/functionality
// It will be used when storing and retrieving the image to/from cache.
let identifier = "com.yourdomain.myprocessor"
// Convert input data/image to target image and return it.
func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> Image? {
switch item {
case .image(let image):
// A previous processor already converted the image to an image object.
// You can do whatever you want to apply to the image and return the result.
return image
case .data(let data):
// Your own way to convert some data to an image.
return createAnImage(data: data)
}
}
}
// Then pass it to the `setImage` methods:
let processor = MyProcessor()
let url = URL(string: "https://example.com/my_image.png")
imageView.kf.setImage(with: url, options: [.processor(processor)])
If you have a prepared CIFilter
, you can create a processor quickly from it.
struct MyCIFilter: CIImageProcessor {
let identifier = "com.yourdomain.myCIFilter"
let filter = Filter { input in
guard let filter = CIFilter(name: "xxx") else { return nil }
filter.setValue(input, forKey: kCIInputBackgroundImageKey)
return filter.outputImage
}
}
CacheSerializer
will be used to convert some data to an image object for retrieving from disk cache and vice versa for storing to the disk cache.
// Just without anything
imageView.kf.setImage(with: url)
// It equals to
imageView.kf.setImage(with: url, options: [.cacheSerializer(DefaultCacheSerializer.default)])
DefaultCacheSerializer
converts cached data to a corresponded image object and vice versa. PNG, JPEG, and GIF are supported by default.
To specify a certain format an image should be, use FormatIndicatedCacheSerializer
. It provides serializers for all built-in supported format: FormatIndicatedCacheSerializer.png
, FormatIndicatedCacheSerializer.jpeg
and FormatIndicatedCacheSerializer.gif
.
By using the DefaultCacheSerializer
, Kingfisher respects the input image data format and try to keep it unchanged. However, sometimes this default behavior might be not what you want. A common case is that, when you using a RoundCornerImageProcessor
, in most cases maybe you want to have an alpha channel (for the corner part). If your original image is JPEG, the alpha channel would be lost when storing to disk. In this case, you may also want to set the png serializer to force converting the images to PNG:
let roundCorner = RoundCornerImageProcessor(cornerRadius: 20)
imageView.kf.setImage(with: url,
options: [.processor(roundCorner),
.cacheSerializer(FormatIndicatedCacheSerializer.png)]
)
Make a type conforming to CacheSerializer
by implementing data(with:original:)
and image(with:options:)
:
struct MyCacheSerializer: CacheSerializer {
func data(with image: Image, original: Data?) -> Data? {
return MyFramework.data(of: image)
}
func image(with data: Data, options: KingfisherParsedOptionsInfo?) -> Image? {
return MyFramework.createImage(from: data)
}
}
// Then pass it to the `setImage` methods:
let serializer = MyCacheSerializer()
let url = URL(string: "https://yourdomain.com/example.png")
imageView.kf.setImage(with: url, options: [.cacheSerializer(serializer)])
You could prefetch some images and cache them before you display them on the screen. This is useful when you know a list of image resources you know they would probably be shown later.
let urls = ["https://example.com/image1.jpg", "https://example.com/image2.jpg"]
.map { URL(string: $0)! }
let prefetcher = ImagePrefetcher(urls: urls) {
skippedResources, failedResources, completedResources in
print("These resources are prefetched: \(completedResources)")
}
prefetcher.start()
// Later when you need to display these images:
imageView.kf.setImage(with: urls[0])
anotherImageView.kf.setImage(with: urls[1])
From iOS 10, Apple introduced a cell prefetching behavior. It could work seamlessly with Kingfisher's ImagePrefetcher
.
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.prefetchDataSource = self
}
extension ViewController: UICollectionViewDataSourcePrefetching {
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
let urls = indexPaths.flatMap { URL(string: $0.urlString) }
ImagePrefetcher(urls: urls).start()
}
}
See WWDC 16 - Session 219 for more about changing of it in iOS 10.
Instead of downloading an image from the network, Kingfisher also supports to set image from a data provider. This is useful when you have local data for images. At the same time, you want to use Kingfisher's options to process and manage them.
LocalFileImageDataProvider
is a type conforming to ImageDataProvider
. It is used to load an image from a local file URL:
let url = URL(fileURLWithPath: path)
let provider = LocalFileImageDataProvider(fileURL: url)
imageView.kf.setImage(with: provider)
You can also pass options to it:
let processor = RoundCornerImageProcessor(cornerRadius: 20)
imageView.kf.setImage(with: provider, options: [.processor(processor)])
Use Base64ImageDataProvider
to provide an image from base64 encoded data. All other features you expected, such as cache or image processors, should work as they are as when getting images from an URL.
let provider = Base64ImageDataProvider(base64String: "\/9j\/4AAQSkZJRgABAQA...", cacheKey: "some-cache-key")
imageView.kf.setImage(with: provider)
Use AVAssetImageDataProvider
to generate an image from a video URL or AVAsset
at a given time:
let provider = AVAssetImageDataProvider(
assetURL: URL(string: "https://example.com/your_video.mp4")!,
seconds: 15.0
)
If you want to create your own image data provider type, conform to ImageDataProvider
protocol by implementing a cacheKey
and a data(handler:)
method to provide image data:
struct UserNameLetterIconImageProvider: ImageDataProvider {
var cacheKey: String { return letter }
let letter: String
init(userNameFirstLetter: String) {
self.letter = userNameFirstLetter
}
func data(handler: @escaping (Result<Data, Error>) -> Void) {
// You can ignore these detail below.
// It generates some data for an image with `letter` being rendered in the center.
let letter = self.letter as NSString
let rect = CGRect(x: 0, y: 0, width: 250, height: 250)
let renderer = UIGraphicsImageRenderer(size: rect.size)
let data = renderer.pngData { context in
UIColor.black.setFill()
context.fill(rect)
let attributes = [
NSAttributedString.Key.foregroundColor: UIColor.white,
.font: UIFont.systemFont(ofSize: 200)
]
let textSize = letter.size(withAttributes: attributes)
let textRect = CGRect(
x: (rect.width - textSize.width) / 2,
y: (rect.height - textSize.height) / 2,
width: textSize.width,
height: textSize.height)
letter.draw(in: textRect, withAttributes: attributes)
}
// Provide the image data in `handler`.
handler(.success(data))
}
}
// Set image for user "John"
let provider = UserNameLetterIconImageProvider(userNameFirstLetter: "J")
imageView.kf.setImage(with: provider)
Maybe you have already noticed, the data(handler:)
contains a callback to you. You can provide the image data in an asynchronous way from another thread if it is too heavy in the main thread.
let path = Bundle.main.path(forResource: "loader", ofType: "gif")!
let data = try! Data(contentsOf: URL(fileURLWithPath: path))
imageView.kf.indicatorType = .image(imageData: data)
imageView.kf.setImage(with: url)
struct MyIndicator: Indicator {
let view: UIView = UIView()
func startAnimatingView() { view.isHidden = false }
func stopAnimatingView() { view.isHidden = true }
init() {
view.backgroundColor = .red
}
}
let i = MyIndicator()
imageView.kf.indicatorType = .custom(indicator: i)
imageView.kf.setImage(with: url, progressBlock: {
receivedSize, totalSize in
let percentage = (Float(receivedSize) / Float(totalSize)) * 100.0
print("downloading progress: \(percentage)%")
myIndicator.percentage = percentage
})
The progressBlock
will be only called if your server response contains the "Content-Length" in the header.
Use .retryStrategy
and DelayRetryStrategy
to implement a simple retry mechanism when setting an image:
let retry = DelayRetryStrategy(maxRetryCount: 5, retryInterval: .seconds(3))
imageView.kf.setImage(with: url, options: [.retryStrategy(retry)])
This will retry the target URL for at most 5 times, with a constant 3 seconds as the interval between each attempt.
You can also choose an .accumulated(3)
as the retry interval, which gives you an accumulated 3 -> 6 -> 9 -> 12 -> 15
retry interval. Or you can even define your own interval pattern by .custom
.
If you need more control for the retry strategy, implement your own type conforming to the RetryStrategy
protocol.
imageView.kf.setImage(with: url, options: [.forceRefresh])
This makes your app to an "offline" mode.
imageView.kf.setImage(with: url, options: [.onlyFromCache])
If the image is not existing in the cache, a .cacheError
with .imageNotExisting
reason will be raised.
By default, all operations, including disk cache loading, are asynchronous for performance. Sometimes, you may want to reload an image (in a table view, for example). If the cached images only exist in the disk cache, you will find a flickering. That is because the image setting is dispatched to an I/O queue first. When the image loading/processing finished, it is dispatched back to the main queue asynchronously.
To change this behavior and get rid of the flickering, you can pass .loadDiskFileSynchronously
when reloading:
let options: [KingfisherOptionsInfo]? = isReloading ? [.loadDiskFileSynchronously] : nil
imageView.kf.set(with: image, options: options)
Storing images to disk cache is an asynchronous operation. However, it is not required to be done before we can set the image view and call the completion handler in view extension methods. In other words, the disk cache may not exist yet in the completion handler below:
imageView.kf.setImage(with: url) { _ in
ImageCache.default.retrieveImageInDiskCache(forKey: url.cacheKey) { result in
switch result {
case .success(let image):
// `image` might be `nil` here.
case .failure: break
}
}
}
This is not a problem for most use cases. However, if your logic depends on the existing of disk cache, pass .waitForCache
as an option. Kingfisher will then wait until the disk cache finishes before calling the handler:
imageView.kf.setImage(with: url, options: [.waitForCache]) { _ in
ImageCache.default.retrieveImageInDiskCache(forKey: url.cacheKey) { result in
switch result {
case .success(let image):
// `image` exists.
case .failure: break
}
}
}
This is only for disk image cache, which involves to async I/O. For the memory cache, everything goes synchronously, so the image should be always in the memory cache.
From iOS 13, Apple allows user to choose to turn on [Low Data Mode] to save cellular and Wi-Fi usage. To respect this setting, you can provide an alternative (usually low-resolution) version of the image and Kingfisher will use that when Low Data Mode is enabled:
imageView.kf.setImage(
with: highResolutionURL,
options: [.lowDataSource(.network(lowResolutionURL)]
)
If there is no network restriction applied by user, highResolutionURL
will be used. Otherwise, when the device is under Low Data Mode and the highResolutionURL
version is not hit in the cache, lowResolutionURL
will be used.
Since .lowDataSource
accept any Source
parameter instead of only a URL, you can also pass in a local image provider to prevent any downloading task:
imageView.kf.setImage(
with: highResolutionURL,
options: [
.provider(LocalFileImageDataProvider(fileURL: localFileURL))
]
)
If
.lowDataSource
option is not provided, thehighResolutionURL
will be always used, regardless of the Low Data Mode setting on the device.
let uiButton: UIButton = //...
uiButton.kf.setImage(with: url, for: .normal)
uiButton.kf.setBackgroundImage(with: url, for: .normal)
let nsButton: NSButton = //...
nsButton.kf.setImage(with: url)
nsButton.kf.setAlternateImage(with: url)
Kingfisher supports to display GIF images.
let imageView: UIImageView = ...
imageView.kf.setImage(with: URL(string: "your_animated_gif_image_url")!)
If you encountered to memory issues when dealing with large GIF, try to use
AnimatedImageView
instead of regular image view to display GIF. It will only decode several frames of your GIF image to get a smaller memory footprint (but high CPU load).
let imageView = AnimatedImageView()
imageView.kf.setImage(with: URL(string: "your_large_animated_gif_image_url")!)
imageView.kf.setImage(
with: URL(string: "your_animated_gif_image_url")!,
options: [.onlyLoadFirstFrame])
It will be useful when you just want to display a static preview of the first frame from a GIF image.
Once a downloading task initialized, even when you set another URL to the image view, that task will continue until finishes.
imageView.kf.setImage(with: url1) { result in
// `result` is `.failure(.imageSettingError(.notCurrentSourceTask))`
// But the download (and cache) is done.
}
// Set again immediately.
imageView.kf.setImage(with: url2) { result in
// `result` is `.success`
}
Although setting for url1
results in a .failure
since the setting task was overridden by url2
, the download task itself is finished. The downloaded image data is also processed and cached.
The downloading and caching operation for the image at url1
is not free, it costs network, CPU time, memory and also, battery.
In most cases, it worths to do that. Since there is a chance that the image is shown to the user again. But if you are sure that you do not need the image from url1
, you can cancel the downloading before starting another one:
imageView.kf.setImage(with: ImageLoader.sampleImageURLs[8]) { result in
// `result` is `.failure(.requestError(.taskCancelled))`
// Now the download task is cancelled.
}
imageView.kf.cancelDownloadTask()
imageView.kf.setImage(with: ImageLoader.sampleImageURLs[9]) { result in
// `result` is `.success`
}
This technology sometimes is useful in a table view or collection view. When users scrolling the list fast, maybe quite a lot of image downloading tasks would be created. You can cancel unnecessary tasks in the didEndDisplaying
delegate method:
func collectionView(
_ collectionView: UICollectionView,
didEndDisplaying cell: UICollectionViewCell,
forItemAt indexPath: IndexPath)
{
// This will cancel the unfinished downloading task when the cell disappearing.
cell.imageView.kf.cancelDownloadTask()
}
Kingfisher is smart enough to cache the processed images and then get it back if you specify the correct ImageProcessor
in the option. Each ImageProcessor
contains an identifier
. It is used when caching the processed images.
Without the identifier
, Kingfisher will not be able to tell which is the correct image in cache. Think about the case you have to store two versions of an image from the same url, one should be round cornered and another should be blurred. You need two different cache keys. In all Kingfisher's built-in image processors, the identifier will be determined by the kind of processor, combined with its parameters for each instance. For example, a round corner processor with 20 as its corner radius might have an identifier
as round-corner-20
, while a 40 radius one's could be round-corner-40
. (Just for demonstrating, they are not that simple value in real)
So, when you create your own processor, you need to make sure that you provide a different identifier
for any different processor instance, with its parameter considered. This helps the processors work well with the cache. Furthermore, it prevents unnecessary downloading and processing.
If you are trying to do one of these:
- Process the same image with different processors to get different versions of the image.
- Process an image with a processor other than the default one, and later need to display the original image.
It worths passing .cacheOriginalImage
as an option. This will store the original downloaded image to cache as well:
let p1 = MyProcessor()
imageView.kf.setImage(with: url, options: [.processor(p1), .cacheOriginalImage])
Both the processed image by p1
and the original downloaded image will be cached. Later, when you process with another processor:
let p2 = AnotherProcessor()
imageView.kf.setImage(with: url, options: [.processor(p2)])
The processed image for p2
is not in cache yet, but Kingfisher now has a chance to check whether the original image for url
being in cache or not. Instead of downloading the image again, Kingfisher will reuse the original image and then apply p2
on it directly.
Think about the case we want to show some large images in a table view or a collection view. In the ideal world, we expect to get smaller thumbnails for them, to reduce downloading time and memory use. But in the real world, maybe your server doesn't prepare such a thumbnail version for you. The newly added DownsamplingImageProcessor
rescues. It downsamples the high-resolution images to a certain size before loading to memory:
imageView.kf.setImage(
with: resource,
placeholder: placeholderImage,
options: [
.processor(DownsamplingImageProcessor(size: imageView.size)),
.scaleFactor(UIScreen.main.scale),
.cacheOriginalImage
])
Typically, DownsamplingImageProcessor
is used with .scaleFactor
and .cacheOriginalImage
. It provides a reasonable image pixel scale for your UI, and prevent future downloading by caching the original high-resolution image.
Installation - Cheat Sheet - FAQ - SwiftUI Support - API Reference - Donate