SwiftUI

How to use Nuke in SwiftUI

Nuke provides first-class SwiftUI support via a FetchImage package that can be installed using Swift Package Manager.

ImageView #

Create an image view using FetchImage and customize it precisely the way you want.

struct ImageView: View {
    let url: URL

    @StateObject private var image = FetchImage()

    var body: some View {
        ZStack {
            Rectangle().fill(Color.gray)
            image.view?
                .resizable()
                .aspectRatio(contentMode: .fill)
                .clipped()
        }
        .onAppear { image.load(url) }
        .onDisappear(perform: image.reset)
    }
}

In iOS 13, use @ObservedObject. Keep in mind that it doesn’t own the object.

FetchImage #

FetchImage is an observable object (ObservableObject) that allows you to manage the download of an image and observe the download status.

public final class FetchImage: 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: UIImage?

    /// 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()
}

Reloading View #

If the URL changes, add .id(url) to your ImageView. It will ensure that onAppear is called when the URL changes.

List #

Usage with a list:

struct DetailsView: View {
    var body: some View {
        List(imageUrls, id: \.self) {
            ImageView(url: $0)
                .frame(height: 200)
        }
    }
}

FetchImage gives you full control over how to manage the download and how to display the image. For example, if you want the download to continue when the view leaves the screen, change the appearance callbacks accordingly.

struct ImageView: View {
    let url: URL

    @StateObject private var image = FetchImage()

    var body: some View {
        // ...
        .onAppear {
            image.priority = .normal
            image.load(url)
        }
        .onDisappear {
            image.priority = .low
        }
    }
}

Animations #

struct ImageView: View {
    let url: URL

    @StateObject private var image = FetchImage()

    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)
}

Grid #

ImageView defined earlier can also be used in grids.

struct GridExampleView: View {
    var body: some View {
        GeometryReader { geometry in
            ScrollView {
                let side = geometry.size.width / 4
                let item = GridItem(.fixed(side), spacing: 2)
                LazyVGrid(columns: Array(repeating: item, count: 4), spacing: 2) {
                    ForEach(demoPhotosURLs.indices) {
                        ImageView(url: demoPhotosURLs[$0])
                            .frame(width: side, height: side)
                            .clipped()
                    }
                }
            }
        }
    }
}

To see grid in action, check out the demo project.