Image loading and caching in SwiftUI
FetchImage
FetchImage makes it easy to download images using Nuke and display them in SwiftUI apps.
Overview
FetchImage
is an observable object (ObservableObject
) that allows you to manage the download of an image and observe the results of the download.
public final class FireFetchImage: ObservableObject, Identifiable {
/// Returns the fetched image.
///
/// - note: In case pipeline has `isProgressiveDecodingEnabled` option enabled
/// and the image being downloaded supports progressive decoding, the `image`
/// might be updated multiple times during the download.
@Published public private(set) var image: PlatformImage?
/// Returns an error if the previous attempt to fetch the most recent attempt
/// to load the image failed with an error.
@Published public private(set) var error: Error?
/// Returns `true` if the image is being loaded.
@Published public private(set) var isLoading: Bool = false
/// The progress of the image download.
@Published public var progress = Progress()
}
Usage
FetchImage
doesn't ship an image view because it's trivial to create one using SwiftUI and customize it precisely the way you want.
struct ImageView: View {
let url: URL
@StateObject private var image = FireFetchImage()
var body: some View {
ZStack {
Rectangle().fill(Color.gray)
image.view?
.resizable()
.aspectRatio(contentMode: .fill)
.clipped()
}
.onAppear { image.load(url) }
.onDisappear(perform: image.reset)
}
}
For iOS 13, use
@ObservedObject
. Keep in mind that@ObservedObject
does not own the instance; you need to maintain a strong reference to theFetchImage
instance.
Lists
struct DetailsView: View {
var body: some View {
List(imageUrls, id: \.self) {
ImageView(url: $0)
.frame(height: 200)
}
}
}
If the URL changes, add
.id(url)
to yourImageView
. This will ensure thatonAppear
is called when the URL changes.
Appearance
FetchImage
gives you full control over how to manage the download and display the image. For example, if you want the download to continue when the view leaves the screen, change the appearance callbacks:
.onAppear {
image.priority = .normal
image.load(url)
}
.onDisappear {
image.priority = .low
}
Animations
struct ImageView: View {
let url: URL
@StateObject private var image = FireFetchImage()
var body: some View {
// ... create image view
.onAppear {
// Ensure that memory cache lookup is performed without animations
withoutAnimation {
image.load(url)
}
}
.onDisappear(perform: image.reset)
.animation(.default)
}
}
private func withoutAnimation(_ closure: () -> Void) {
var transaction = Transaction(animation: nil)
transaction.disablesAnimations = true
withTransaction(transaction, closure)
Firebase
You may also initialize a FetchImage
using a Firestore StorageReference
. These references can be easily created synchronously, but require an asynchronous call in order generate URLs for fetching the requested content. Unfortunately, this makes image loading in SwiftUI rather difficult. Using Nuke
and Firebase
together simplifies the whole process quite a bit:
@State referencedImage: StorageReference
public var body: some View {
ImageView(image: FireFetchImage(regularStorageRef: referencedImage))
.animation(.default)
}
Requirements
Nuke | Swift | Xcode | Platforms |
---|---|---|---|
FetchImage | Swift 5.1 | Xcode 11.3 | iOS 13.0 / watchOS 6.0 / macOS 10.15 / tvOS 13.0 |