iOS: Kingfisher, Nuke and BlurHash Placeholders

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

See the section bibliography for the reference material consulted while preparing these pages.

Baseline date: 2026-10-07. Check each library’s README for its current version and minimum iOS version before adopting it, since the SwiftUI APIs below have changed between major releases. The platform’s own capture, photo library and video APIs are in Camera, Media and Photos. This page is about loading, caching and placeholder rendering of remote images from a media backend, see responsive images and thumbnails.

AsyncImage and what it does not do

AsyncImage downloads a URL and shows a placeholder, then the image, optionally with a custom phase handler and a transaction animation.

AsyncImage(url: coverURL) { phase in
    switch phase {
    case .success(let image): image.resizable().scaledToFill()
    case .failure: Image(systemName: "photo")
    default: ProgressView()
    }
}
.frame(width: 160, height: 240)
.clipped()

It has no image cache of its own beyond the shared URLCache, so scrolling back through a list can download images again. It offers no prefetching, no downsampling (a large file is decoded at full size, which costs memory), no control over the cache or the request, and no support for a BlurHash placeholder beyond what you place in the closure. Use it for a handful of small images. For lists and grids, use a library, or build on URLSession and ImageIO yourself.

Kingfisher

Kingfisher provides a memory and disk cache, processors and SwiftUI views.

import Kingfisher
import SwiftUI

struct CoverView: View {
    let url: URL
    let blurhash: String?

    var body: some View {
        KFImage(url)
            .placeholder { BlurHashPlaceholder(hash: blurhash) }
            .setProcessor(DownsamplingImageProcessor(size: CGSize(width: 160, height: 240)))
            .scaleFactor(UIScreen.main.scale)
            .cacheOriginalImage()
            .fade(duration: 0.25)
            .resizable()
            .scaledToFill()
            .frame(width: 160, height: 240)
            .clipped()
    }
}

DownsamplingImageProcessor uses ImageIO to decode the file straight to the target size, so a 1280 pixel image is never fully decoded for a 160 point slot. scaleFactor makes the target size pixel-accurate on Retina screens. Prefetch the next page of covers with ImagePrefetcher(urls:).start(). Reference: Kingfisher cheat sheet and the API documentation.

Nuke

Nuke separates the pipeline (ImagePipeline), the SwiftUI view (LazyImage in the NukeUI module) and prefetching (ImagePrefetcher).

import Nuke
import NukeUI
import SwiftUI

struct CoverView: View {
    let url: URL
    let blurhash: String?

    var body: some View {
        LazyImage(request: ImageRequest(url: url, processors: [.resize(width: 160)])) { state in  // points; Nuke applies the screen scale
            if let image = state.image {
                image.resizable().scaledToFill()
            } else {
                BlurHashPlaceholder(hash: blurhash)
            }
        }
        .frame(width: 160, height: 240)
        .clipped()
    }
}

let prefetcher = ImagePrefetcher()
prefetcher.startPrefetching(with: nextPageURLs)

Nuke resizes in the pipeline, caches the decoded and the original data, and coalesces duplicate requests. Configure a larger cache with ImagePipeline.Configuration (a DataCache for the disk), and share one pipeline. Reference: Nuke documentation and the README.

SDWebImage

SDWebImage is the long-established alternative, with an Objective-C core, the SDWebImageSwiftUI package for SwiftUI (WebImage(url:)), and coder plugins for formats such as WebP and AVIF (for older iOS versions that lack system support). Choose it if you already use it, or you need its coder plugins.

Library Choose it when

AsyncImage

Few small images, no caching needs.

Kingfisher

You want a mature, easy API with processors and a SwiftUI view.

Nuke

You want a pipeline you can configure and prefetch, and a small, modern API.

SDWebImage

You have existing usage, or need its extra codecs.

Formats on Apple platforms

iOS and macOS decode JPEG, PNG, GIF, HEIC and, from iOS 14, WebP through ImageIO. AVIF decoding was added in iOS 16. Check the ImageIO documentation for the exact versions and ask the API for the format the device supports, as in the bestVariant helper of the Android page, using #available(iOS 16, *).

BlurHash with the woltapp Swift implementation

The Swift implementation in the reference repository is meant to be copied into the project. It adds an initialiser to UIImage:

struct BlurHashPlaceholder: View {
    let hash: String?

    var body: some View {
        if let hash, let image = UIImage(blurHash: hash, size: CGSize(width: 32, height: 32), punch: 1) {
            Image(uiImage: image).resizable().scaledToFill()
        } else {
            Color.gray.opacity(0.2)
        }
    }
}

Decode at 32 by 32 points and let SwiftUI scale it. Cache the decoded UIImage per hash (an NSCache), because the view body can run many times, and never decode in body for a long list without that cache. The algorithm is in the BlurHash page. The package also has an encoder, for when the app computes the hash before an upload, see device-side compression and uploads.