Oct 11, 2022 iOS

How to use SwiftUI AsyncImage

How to use SwiftUI AsyncImage : Images and videos power almost all contemporary apps and websites. All of the visuals you see on social networking, entertainment, and commerce applications come from a backend source. Since several backend calls are required for this, if you really waited for all of those photos to load before displaying the website, it would take a long time for the applications to load, which would have a negative impact on the user experience.

A fairly straightforward but effective solution to this problem is to load the picture asynchronously and display a placeholder image till that time. Almost all apps that asynchronously load and cache photos follow this identical methodology 

SwiftUI AsyncImage is a new built-in view that SwiftUI 2021 brings. It provides a quick way to download and render a remote image from a URL. At the most fundamental level, if all we needed to do was render a downloaded image exactly as it is, we can now accomplish so as follows:

A built-in view called AsyncImage allows for the asynchronous loading and showing of distant pictures. All you have to do is provide the picture URL. The difficult task of obtaining the distant picture and displaying it on screen is then handled by AsyncImage.

I’ll demonstrate how to use AsyncImage in SwiftUI apps in this article. You need Xcode 13 and iOS 15 in order to follow the lesson.

 AsyncImage(url: URL(string: "https://leadbycode.com/TestImage.jpeg")) // 1       
struct ContentView: View {
    var body: some View {
        AsyncImage(url: URL(string: "https://example.com/image.jpg")) { image in
            image.resizable()
        } placeholder: {
            ProgressView()
        }
    }
}

In this example, we create an AsyncImage with a remote URL and a closure that returns a resizable image. We also provide a placeholder ProgressView to display while the image is loading.

The AsyncImage view has two required parameters:

  • url: The remote URL of the image to load.
  • content: A closure that takes a UIImage (or NSImage on macOS) and returns a View.

You can also provide an optional placeholder parameter, which is a closure that returns a View to display while the image is loading.

When you run this code, SwiftUI will automatically download the image asynchronously and display it once it’s loaded. If there’s an error loading the image, the content closure won’t be called and the placeholder view will be displayed instead.

Note that AsyncImage requires iOS 15 or later, macOS 12 or later, or watchOS 8 or later.

The fundamentals of an SwiftUI AsyncImage

There are a several initializers included with AsyncImage, the most basic of which accepts an optional URL as an argument. As a result, you don’t need to manually unwrap the URL; instead, AsyncImage will either show the picture if loading was successful or a grey placeholder by default.

Start by adding a copy of SwiftUI AsyncImage to the project you’re working on.

struct ContentView: View {
    var body: some View {
        AsyncImage(url: URL(string: "https://leadbycode.com/TestImage.jpeg"))
    }
}

Using Placeholder views and appropriately altering SwiftUI AsyncImages

SwiftUI AsyncImage has initializers that allow access to the loaded image in order to influence how the picture is shown once it has been loaded. You may utilise the content closure provided by the init(url:scale:content:placeholder) initializer, which accepts an Image instance and produces a View. As a consequence, we may utilise it to add modifiers to the final image. To correctly scale the picture, we may, for instance, add a.resizable or.aspectRatio modifier.

struct ContentView: View {
    var body: some View {
        AsyncImage(url: URL(string: "https://leadbycode.com/TestImage.jpeg"), scale: 3) { image in
            image
              .resizable()
              .aspectRatio(contentMode: .fill)
        } placeholder: {
            Color.blue
        }
            .frame(height: 300)
    }
}

One thing to bear in mind, though, is that the view provided by the placeholder closure is used as a fallback in case an error occurred in addition to being utilised while the picture is being loaded. Again, it could be OK for some use scenarios, but in the example above, we wanted to show a ProgressView while our picture was being loaded. However, if the download failed, we probably don’t want to keep showing the loading spinner.

Fortunately, there is a solution to deal with it as well. We can create a closure that generates the view’s content based on what AsyncImagePhase the download process is currently in by using the third type of initializer that AsyncImage presently ships with.

We may additionally supply a few more arguments, such as the scale we anticipate the downloaded picture to have. However, it doesn’t appear like we can have any influence over the download process itself. There doesn’t appear to be a way to add any more cache layers on top of URLSession’s built-in caching, according to the documentation for AsyncImage, which notes that it will always utilise the app’s default URLSession.shared instance to conduct each network request.

Nevertheless, I believe that SwiftUI AsyncImage is a very welcome addition to SwiftUI’s collection of built-in views and that it will be quite helpful in a variety of applications.

struct ContentView: View {

    var body: some View {
        VStack {
            AsyncImage(url: URL(string: "https://leadbycode.com/TestImage.jpeg")) { phase in
                switch phase {
                case .empty:
                    ProgressView()
                case .success(let image):
                    image.resizable()
                         .aspectRatio(contentMode: .fit)
                         .frame(maxWidth: 300, maxHeight: 100)
                case .failure:
                    Image(systemName: "photo")
                @unknown default:
                    EmptyView()
                }
            }
            ...
        }
    }
}

Animation Added to Transaction for swiftUI AsyncImage

When the phase changes, the same init enables you define an optional transaction. For instance, the code snippet that follows instructs the transaction parameter to utilise a spring animation:

When the image is downloaded in this manner, you will see a fade-in animation. The code won’t function if you test it in the preview window. To view the animation, please be sure to test the code in a simulator.

struct ContentView: View {

    var body: some View {
        VStack {
            AsyncImage(url: URL(string: "https://leadbycode.com/TestImage.jpeg"), transaction: Transaction(animation: .spring())) { phase in
    switch phase {
    case .empty:
        Color.blue.opacity(0.1)
 
    case .success(let image):
        image
            .resizable()
            .aspectFit()
 
    case .failure(_):
                    Image(systemName: "photo")
                    .resizable()
 
    @unknown default:
                    Image(systemName: "photo")
    }
}
.frame(width: 300, height: 500)
.cornerRadius(20)
            }
        }
}

When working with images in SwiftUI, you can use the AsyncImage view to asynchronously load and display images. To handle errors that might occur during the image loading process, you can use the init(url:scale:transaction:content:) initializer and provide a closure that returns a fallback view in case of an error.

Here’s an example of how to handle errors for the AsyncImage view:

AsyncImage(url: URL(string: "https://example.com/image.png")) { phase in
    switch phase {
    case .success(let image):
        image
            .resizable()
            .aspectRatio(contentMode: .fit)
    case .failure(let error):
        Text("Failed to load image")
            .foregroundColor(.red)
    case .empty:
        Text("Loading...")
            .foregroundColor(.gray)
    @unknown default:
        Text("Loading...")
            .foregroundColor(.gray)
    }
}

In this example, the closure passed to AsyncImage takes a phase parameter that represents the current state of the image loading process. If the image loads successfully, the closure returns the loaded image wrapped in a resizable modifier. If an error occurs during the loading process, the closure returns a text view with an error message. If the image is still loading, the closure returns a text view with a loading message.

Note that there is also an @unknown default case in the switch statement to handle any future cases that might be added to the AsyncImagePhase enum. It’s important to provide a default case to avoid crashing in case of any unexpected behavior.

Index