AsyncImage in SwiftUI

Almost all the modern apps and websites are driven by images and videos. The images you see on shopping apps, social media and entertainment apps are all loaded from some backend source. Since there are a lot of backend calls needed for this, if you actually waiting for all those images to load before showing the website, it would take immense amount of time to load the apps - which would result in bad user experience.

A very simple but efficient solution for that is loading the image asynchronously and showing a placeholder as the image till then. This is the exact approach used in almost all the apps where the images are asynchronously loaded and cached (in the best case scenerio).

You can implement asynchronous image loading in SwiftUI for iOS 15+ using AsyncImage. Without further ado, let's take a look at it.

Basic Implementation

The easiest way you can load an image with AsyncImage is with a URL.

 AsyncImage(url: URL(string: "https://swiftanytime-content.s3.ap-south-1.amazonaws.com/SwiftUI-Beginner/Async-Image/TestImage.jpeg")) // 1       

Result:

Code Explanation:

  1. Async Image is initialized with a URL - which is initialized with a string.
Note here, the image specific initializers like .resizeable() and .contentMode(aspectRatio:) cannot be used.
For playing around with async image, feel free to use this test URL.

AsyncImage with Image specific modifiers

Many times, while working in production apps, using .resizable() and .aspectRatio(contentMode: ...) for images become inevitable. Like in the previous section, you can observe how the image was taking up all the available space. What if you don't want to change the content mode and resizeability in order to fix that? Let's try AsyncImage(url: ..., content: ..., placeholder: ...) initialization for that.

  AsyncImage(url: URL(string: "https://swiftanytime-content.s3.ap-south-1.amazonaws.com/SwiftUI-Beginner/Async-Image/TestImage.jpeg")) { image in
            image
                .resizable()
                .aspectRatio(contentMode: .fill)
                
        } placeholder: {
            Color.gray
        }
        .frame(width: 250, height: 250)

Result:

And in the case you only want to control the scale of the image, you can use the AsyncImage with the scale modifier. The default value of the scale is 1. Let's try using scale value of 2.

   AsyncImage(url: URL(string: "https://swiftanytime-content.s3.ap-south-1.amazonaws.com/SwiftUI-Beginner/Async-Image/TestImage.jpeg")!, scale: 2)
            .frame(width: 250, height: 250)

Result:

Image with placeholder while loading

If a blank view while loading the image seems boring and less indicative, let's try to add a placeholder view like a progress spinner or an SF symbol.

  AsyncImage(url: URL(string: "https://swiftanytime-content.s3.ap-south-1.amazonaws.com/SwiftUI-Beginner/Async-Image/TestImage.jpeg")!) { image in
            image
                .resizable()
                .aspectRatio(contentMode: .fill)
        } placeholder: {
            Image(systemName: "photo.fill")
        }.frame(width: 250, height: 250)

Result:

Error handling for the images

That was all about framing and adding placeholder to async image. But what if the image URL is invalid. SwiftUI allows us to use a view of your choice in the case the image URL is nil. For that, you can use the transaction parameter which allows you to handle the states of loading the image.

​​  AsyncImage(url: URL(string: "AWrongURL")!) { phase in // 1 
            if let image = phase.image { // 2
                // if the image is valid
                image
                    .resizable()
                    .aspectRatio(contentMode: .fill)
            } else if phase.error != nil { // 3  
                // some kind of error appears
                Text("No image available")
            } else {
                //appears as placeholder image 
                Image(systemName: "photo") // 4
                    .resizable()
                    .aspectRatio(contentMode: .fill)
            }
        }.frame(width: 250, height: 250, alignment: .center)

Result considering an invalid url was used:

Code Explanation:

  1. Here, the async image is initialized and we have defined phase variable in the block. The phase variable changes as the image loads. The load phases can be - empty, success, error
  2. Using an if let, the phase image is checked and displayed.
  3. Checking if the error is nil. If it is not, some text is displayed
  4. Default image if none of the above conditions are satisfied.

To understand image phases and transactions better, let's try another initializer - init(url:scale:transaction:content).

 AsyncImage(url: URL(string:"https://swiftanytime-content.s3.ap-south-1.amazonaws.com/SwiftUI-Beginner/Async-Image/TestImage.jpeg")!, scale: 2) { phase in // 1 
            if let image = phase.image { // 2 
                            // if the image is valid
                            image
                                .resizable()
                                .aspectRatio(contentMode: .fill)
                        } else if phase.error != nil { // 3 
                            // some kind of error appears
                            Text("404! \n No image available 😢")
                                .bold()
                                .font(.title)
                                .multilineTextAlignment(.center)
                                
                        } else { // 4 
                            // showing progress view as placeholder
                          ProgressView()
                                .font(.largeTitle)
                        }
        }.padding()

Result:

In the case you try entering a wrong URL, this is what the app shows.

Code Explanation:

  1. An Async image is defined here with the scale of 2 and a test url. Along with that, a phase block is defined where you handle all the image states. You can observe that is the scale of the image is doubled in the result gif.
  2. If the image phase is not nil, the image is presented in resizeable way and fill aspect ratio.
  3. If the error is not nil, some 404 text is shown.
  4. If neither of the earlier situations are called, just an activity indicator in large title style is shown.

Congratulations! You deserve to celebrate this moment. Today you learned about Async Image implementation, adding a placeholder to it, using image-specific modifiers and error handling the URL.. We encourage you to read more related articles like Colors and Gradient in SwiftUI till then, have a great time ahead.

Eat. Sleep. Swift Anytime. Repeat.


We at Swift Anytime have the mission to make learn iOS development the way everyone enjoys it. You can check out our articles on SwiftUI, Swift, iOS Interview Questions and get started with your iOS journey today.​​

You've successfully subscribed to Swift Anytime
Great! Next, complete checkout to get full access to all premium content.
Error! Could not sign up. invalid link.
Welcome back! You've successfully signed in.
Error! Could not sign in. Please try again.
Success! Your account is fully activated, you now have access to all content.
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.