SwiftUI · · 4 min read

Using AsyncImage in SwiftUI for Loading Images Asynchronously

Using AsyncImage in SwiftUI for Loading Images Asynchronously

In WWDC 2021, Apple announced tons of new features for the SwiftUI framework to make developers’ life easier. AsyncImage is definitely one of the new views, introduced in iOS 15, that is worth a mention. If your app needs to retrieve and display images from remote servers, this new view should save 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 tutorial, I will show you how to work with AsyncImage in SwiftUI projects. To follow the tutorial, please make sure you use Xcode 13 and iOS 15.

The Basic Usage of AsyncImage

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.

asyncimage-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-scale-image

Customizing the Image Size and PlaceHolder

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.

asyncimage-swiftui-change-image-size

Handling Different Phases of the Asynchronous Operation

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.

swiftui-asyncimage-phase

Adding Animation with Transaction

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.

swiftui-asyncimage-animation

Read next