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)
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 aURLRequest
) - 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.
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:
SDWebImageSwiftUI | url-image | NukeUI | |
GitHub Stars | ~ 1.3k | ~ 930 | ~360 |
Last Release | March 2021 (2.0.2) | May 2021 (3.0.0) | January 2022 (0.8.0) |
Supports Animated Images | Yes | No | Yes |
iOS support | v13 | v12 | v12 |
macOS support | v10_15 | v10_13 | v10_14 |
watchOS support | v6 | v4 | v5 |
tvOS support | v13 | v12 | v12 |
Distribution | SPM, CocoaPods, Carthage | SPM | SPM, 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.