Skip to content

AsyncImage in SwiftUI

SwiftUI’s AsyncImage view simplifies the process of loading and displaying remote images asynchronously. It provides built-in support for placeholders and error handling, making it an essential tool for modern app development. This tutorial will guide you through the basics and demonstrate unique examples that cater to developers of all levels.

Introduction to AsyncImage

AsyncImage is a view that asynchronously loads and displays an image from a specified URL. You can easily add a placeholder, handle errors, and manipulate the loaded image.

Basic Usage of AsyncImage

Let’s start with a basic example of loading an image from a URL and displaying it in a SwiftUI view.

import SwiftUI

struct BasicAsyncImageView: View {
    let imageUrl = URL(string: "https://picsum.photos/200")

    var body: some View {
        AsyncImage(url: imageUrl)
            .frame(width: 200, height: 200)
    }
}

In this example, we load an image from a URL and display it within a 200×200 frame. Until the image loads, SwiftUI displays a default placeholder.

Customizing the Placeholder

You can customize the placeholder to enhance the user experience while the image is loading.

import SwiftUI

struct PlaceholderAsyncImageView: View {
    let imageUrl = URL(string: "https://picsum.photos/200")

    var body: some View {
        AsyncImage(url: imageUrl) { image in
            image
                .resizable()
                .aspectRatio(contentMode: .fit)
        } placeholder: {
            ProgressView("Loading...")
                .frame(width: 200, height: 200)
        }
    }
}

Here, we use ProgressView as a custom placeholder, which is displayed until the image is fully loaded.

Handling Errors

It’s crucial to handle errors gracefully, providing a fallback UI when the image fails to load.

import SwiftUI

struct ErrorHandlingAsyncImageView: View {
    let imageUrl = URL(string: "https://picsum.photos/200")

    var body: some View {
        AsyncImage(url: imageUrl) { phase in
            switch phase {
            case .empty:
                ProgressView("Loading...")
                    .frame(width: 200, height: 200)
            case .success(let image):
                image
                    .resizable()
                    .aspectRatio(contentMode: .fit)
            case .failure:
                Image(systemName: "exclamationmark.triangle")
                    .resizable()
                    .frame(width: 50, height: 50)
                    .foregroundColor(.red)
            @unknown default:
                EmptyView()
            }
        }
        .frame(width: 200, height: 200)
    }
}

In this example, we handle different loading phases using a switch statement. If the image fails to load, we display a system symbol as an error indicator.

Advanced Example: Image Grid

Let’s create an advanced example where we display a grid of asynchronously loaded images.

import SwiftUI

struct ImageGridAsyncView: View {
    let urls = [
        URL(string: "https://picsum.photos/200/300"),
        URL(string: "https://picsum.photos/200"),
        URL(string: "https://picsum.photos/300"),
        URL(string: "https://picsum.photos/400")
    ]

    var body: some View {
        let columns = [
            GridItem(.flexible()),
            GridItem(.flexible())
        ]
        
        ScrollView {
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(urls, id: \.self) { url in
                    AsyncImage(url: url) { phase in
                        switch phase {
                        case .empty:
                            ProgressView()
                                .frame(width: 100, height: 100)
                        case .success(let image):
                            image
                                .resizable()
                                .aspectRatio(contentMode: .fill)
                                .frame(width: 100, height: 100)
                                .clipped()
                        case .failure:
                            Image(systemName: "exclamationmark.triangle")
                                .resizable()
                                .frame(width: 50, height: 50)
                                .foregroundColor(.red)
                        @unknown default:
                            EmptyView()
                        }
                    }
                }
            }
            .padding()
        }
    }
}

 

 

Back To Top