Load Image from URL with AsyncImage in SwiftUI

swiftui Sep 16, 2024
AsyncImage in SwiftUI

Almost all the modern apps and websites are driven by images and videos. The images you see on shopping, social media or entertainment apps are all loaded from some backend source. Since there are a lot of backend calls needed for this, if an app or website actually waits for all the images to load before displaying the content, it can significantly slow down the loading time, leading to a poor user experience.

An efficient solution to solve this problem is to load the image asynchoronously and to show a placeholder image in the meantime. This approach is widely used across apps, where images are loaded in the background and cached for future use.

Table of Contents

  1. How to Load and Display Image from URL with AsyncImage
  2. Resize images with AsyncImage
  3. AsyncImage with custom placeholder while loading
  4. AsyncImage Error Handling

 

How to Load and Display Image from URL with AsyncImage

You can implement asynchronous image loading in SwiftUI for iOS 15+ using AsyncImage view. The initialiser takes in a URL as the parameter to load an image from the specified URL and then displays it.

struct ContentView: View {
    var body: some View {
        AsyncImage(url: URL(string: "https://picsum.photos/id/237/300/300"))
    }
} 

Result:

If you observe closely, you would see a gray colored placeholder while the image is loading. By default, AsycImage displays a placeholder that fills the entire available space, irrespective of the image size. In the example above, the puppy image is smaller than the overall frame, and that is why it appears smaller than the placeholder.

 

Resize images with AsyncImage

Many a times, while working on apps, using .resizable() and .aspectRatio()modifiers for images become inevitable. But these image specific modifiers can't be used directly on the AsyncImage view.

To resize the images, you'll have to use another initialiser AsyncImage(url: ..., content: ..., placeholder: ...) where you can apply these modifiers to the Image instance that the content closure receives.

AsyncImage(url: URL(string: "https://picsum.photos/id/237/400/400")) { image in
    image
        .resizable()
        .aspectRatio(contentMode: .fill)
} placeholder: {
    Color.gray
}
.frame(width: 250, height: 250)

Result:

While using this AsyncImage initialiser, you also need to explicitly specify the placeholder which is set to a color in the above example. We'll discuss more about the custom placeholder in the next section.

 

AsyncImage with custom placeholder while loading

If the default placeholder while loading the image seems boring and less indicative, you can also add a placeholder view like a progress bar or an SF symbol.

To prevent the placeholder to take up the entire available space of the frame, you can specifiy the width and the height of the AsyncImage view using the .frame() modifier to ensure that the placeholder matches the size of the image.

AsyncImage(url: URL(string: "https://picsum.photos/id/237/400/400")) { image in
    image
        .resizable()
        .aspectRatio(contentMode: .fill)
} placeholder: {
    Image(systemName: "photo.fill")
}
.frame(width: 250, height: 250)
.border(Color.gray)

Result:

 

AsyncImage Error Handling

One problem with the approaches we've discussed so far is, even if say due to certain reasons the image is unable to load from the URL, the user will be shown the placeholder infinitely without explaining what exactly the problem is.

To gain more control over this loading process, you can use another initialiser of AsyncImage with the content parameter which is a closure that takes the AsyncImagePhase as an input.

AsyncImagePhase is an enum which has three cases: empty, success and failureindicating the loading phases of the image. You can either use if else or switch to iterate over these enum cases.

AsyncImage(url: URL(string: "https://picsum.photos/i/237/400/400")) { phase in
    if let image = phase.image {
        image
            .resizable()
            .aspectRatio(contentMode: .fill)
    } else if phase.error != nil {
        Text("No image available")
    } else {
        Image(systemName: "photo")
    }
}
.frame(width: 250, height: 250)
.border(Color.gray)

In the above code:

  1. If the image loads successfully, the .success case is triggered and the if condition is met which displays the loaded image.
  2. If there is any error, the .failure case is triggered and the if else condition is met. In this case, we have shown the message "No image available".
  3. While the image is not loaded, the .empty case is triggered and the else condition is met which displays the placeholder.

Result if an invalid URL is used:

 

Where to go next?

Congratulations! You deserve to celebrate this moment. Today you learned about AsyncImage implementation, adding a placeholder to it, using image specific modifiers and error handling. We encourage you to read more related articles like ProgressView in SwiftUI and Complete Guide to List in SwiftUI.

Signup now to get notified about our
FREE iOS Workshops!