In WWDC 2021, Apple announced tons of new features for the SwiftUI framework to make developers’ life easier. AsyncImage was definitely one of the most anticipated features, introduced in iOS 15. If your app needs to retrieve and display images from remote servers, this view saves you from writing your own code to handle asynchronous download.
AsyncImage is a built-in view for loading and displaying remote images asynchronously. All you need is to tell it what the image URL is. AsyncImage then does the heavy lifting to grab the remote image and show it on screen.
In this chapter, I will show you how to work with AsyncImage in SwiftUI projects.
The simplest way to use AsyncImage is by specifying the image URL like this:
AsyncImage(url: URL(string: imageURL))
AsyncImage then connects to the given URL and download the remote image asynchronously. It also automatically renders a placeholder in gray while the image is not yet ready for display. Once the image is completely downloaded, AsyncImage displays the image in its intrinsic size.

Assuming that you've created a new SwiftUI project in Xcode, you can replace the code in ContentView like this to have a try:
struct ContentView: View {
let imageURL = "https://link.appcoda.com/testimage"
var body: some View {
AsyncImage(url: URL(string: imageURL))
}
}
In the preview canvas, you should immediately see a placeholder in gray, followed by the image. It takes a few seconds for the image to download. Thus, iOS displays the placeholder.
If you want to make the image smaller or larger, you can pass a scaling value to the scale parameter like this:
AsyncImage(url: URL(string: imageURL), scale: 2.0)
A value greater than 1.0 will scale down the image. Conversely, a value less than 1 will make the image bigger.

AsyncImage provides another constructor for developers if you need further customization:
init<I, P>(url: URL?, scale: CGFloat, content: (Image) -> I, placeholder: () -> P)
By initializing AsyncImage using the init above, we can resize and scale the downloaded image to the preferred size. On top of that, we can provide our own implementation for the placeholder. Here is a sample code snippet:
AsyncImage(url: URL(string: imageURL)) { image in
image
.resizable()
.scaledToFill()
} placeholder: {
Color.purple.opacity(0.1)
}
.frame(width: 300, height: 500)
.cornerRadius(20)
In the code above, AsyncImage provides the resulting image for manipulation. We then apply the resizable() and scaledToFill() modifier to resize the image. For the AsyncImage view, we limit its size to 300×500 points.
The placeholder parameter allows us to create our own placeholder instead of using the default one. Here, we display a placeholder in light purple.

The AsyncImage view provides another constructor if you need to provide a better control for the asynchronous download operation:
init(url: URL?, scale: CGFloat, transaction: Transaction, content: (AsyncImagePhase) -> Content
AsyncImagePhase is an enum that keeps track of the current phase of the download operation. You can provide detailed implementation for each of the phases including empty, failure, and success.
Here is a sample code snippet:
AsyncImage(url: URL(string: imageURL)) { phase in
switch phase {
case .empty:
Color.purple.opacity(0.1)
case .success(let image):
image
.resizable()
.scaledToFill()
case .failure(_):
Image(systemName: "exclamationmark.icloud")
.resizable()
.scaledToFit()
@unknown default:
Image(systemName: "exclamationmark.icloud")
}
}
.frame(width: 300, height: 300)
.cornerRadius(20)
The empty state indicates that the image is not loaded. In this case, we display a placeholder. For the success state, we apply a couple of modifiers and display it on screen. The failure state allows you to provide an alternate view if there is any errors. In the code above, we simply display a system image. If you updated the imageURL to an invalid URL, the app now displays an exclamation mark image.

The same init lets you specify an optional transaction when the phase changes. For example, the following code snippet specifies to use a spring animation in the transaction parameter:
AsyncImage(url: URL(string: imageURL), transaction: Transaction(animation: .spring())) { phase in
switch phase {
case .empty:
Color.purple.opacity(0.1)
case .success(let image):
image
.resizable()
.scaledToFill()
case .failure(_):
Image(systemName: "exclamationmark.icloud")
.resizable()
.scaledToFit()
@unknown default:
Image(systemName: "exclamationmark.icloud")
}
}
.frame(width: 300, height: 500)
.cornerRadius(20)
By doing so, you will see a fade-in animation when the image is downloaded. If you test the code in the preview pane, it won’t work. Please make sure you test the code in a simulator to see the animation.
You can also attach the transition modifier to the image view like this:
case .success(let image):
image
.resizable()
.scaledToFill()
.transition(.slide)
This creates a slide-in animation when displaying the resulting image.
In this chapter, we showed you a very useful view called AsyncImage. With this feature, it is very easy to download and display remote images. All you need is specify the URL of the image and the AsyncImage view will do all the heavy liftings for you.
For reference, you can download the complete demo project here: