When to use built-in, 3rd party or a custom implementation for Async Image Loading in SwiftUI

When to use built-in, 3rd party or a custom implementation for Async Image Loading in SwiftUI

In this blog post I reflect on the various options to have async image loading in SwiftUI:

  • built-in option with AsyncImage
  • reusing open-source projects (e.g. SDWebImageSwiftUI)
  • implement a custom solution

I'll share my personal experiences and recommendations on when to use what.

SwiftUI's built-in option

SwiftUI on iOS 15 has AsyncImage that allows us to load images from the network.

AsyncImage(url: URL(string: "https://example.com/icon.png")) { image in
    image.resizable()
} placeholder: {
    ProgressView()
}
.frame(width: 50, height: 50)

Apple Documentation

The obvious drawback is that iOS 15+ is required. Other things to consider are:

  • no caching (other than what’s already offered by URLSession)
  • only works with a URL (rather than also accepting a URLRequest)
  • does not support animated images (GIF).

Open-source projects

These three popular open-source projects do offer SwiftUI views and underlying image loading capabilities:

All three projects are published under the MIT license.

Screen Shot 2022-02-03 at 6.25.54 AM.png

Screen Shot 2022-02-03 at 6.25.21 AM.png

Screen Shot 2022-02-03 at 6.25.40 AM.png

In one of my projects I started with url-image but I switched to SDWebImageSwiftUI because I needed support for animated images.

Here are some stats (as of Feb 3rd 2022) to compare:

SDWebImageSwiftUIurl-imageNukeUI
GitHub Stars~ 1.3k~ 930~360
Last ReleaseMarch 2021 (2.0.2)May 2021 (3.0.0)January 2022 (0.8.0)
Supports Animated ImagesYesNoYes
iOS supportv13v12v12
macOS supportv10_15v10_13v10_14
watchOS supportv6v4v5
tvOS supportv13v12v12
DistributionSPM, CocoaPods, CarthageSPMSPM, CocoaPods

Custom implementation

Do you wan to go with a custom implementation? Read Using Swift’s async/await to build an image loader from Donny Walls to learn how to do that. It features the use of modern Swift concurrency (async/await)

Here's what the SwiftUI view's implementation could look like:

struct RemoteImage: View {
    private let source: URLRequest
    @State private var image: UIImage?

    @Environment(\.imageLoader) private var imageLoader

    init(source: URL) {
        self.init(source: URLRequest(url: source))
    }

    init(source: URLRequest) {
        self.source = source
    }

    var body: some View {
        Group {
            if let image = image {
                Image(uiImage: image)
            } else {
                Rectangle()
                    .background(Color.red)
            }
        }
        .task {
            await loadImage(at: source)
        }
    }

    func loadImage(at source: URLRequest) async {
        do {
            image = try await imageLoader.fetch(source)
        } catch {
            print(error)
        }
    }
}

Recommendation

If your app or framework supports iOS 15+ and you don't need advanced caching capabilities or animated images, go ahead with AsyncImage from SwiftUI.

Otherwise look at one of the open-source projects mentioned in this blog post.

If you have very particular requirements and you want/need to avoid external dependencies, you might want to try to build your own image loader. Personally I would try to avoid this as much as possible.

Did you find this article valuable?

Support Marco Eidinger by becoming a sponsor. Any amount is appreciated!