Skip to content

AsyncImage (SwiftUI)

A SwiftUI guide for coding agents. Also covers remote image loading, asyncimage placeholder, image download url swiftui, asyncimagephase, image loading states ios, avatar from url.

AsyncImage downloads an image from a URL and displays it, with no networking code. Reach for it whenever a view shows a remote image and the shared URLSession cache is good enough.

Basics

Pass a URL and the view handles download, caching, and display. It is a normal SwiftUI view, so frame, background, and clip modifiers apply as usual.

struct AvatarView: View {
  let url: URL

  var body: some View {
    AsyncImage(url: url)
      .frame(width: 44, height: 44)
      .background(Color.gray)
      .clipShape(Circle())
  }
}

The view uses the shared URLSession. You cannot inject a custom URLCache or shape the URLRequest, so apps that need custom caching or headers need their own loader.

Content and Placeholder

A second initializer hands you the loaded Image in a ViewBuilder closure, so you can make it resizable and set the content mode. The placeholder closure shows while the download runs.

AsyncImage(url: url) { image in
  image
    .resizable()
    .scaledToFill()
} placeholder: {
  ProgressView()
}
.frame(width: 44, height: 44)
.clipShape(Circle())

Both closures are ViewBuilders, so any modifier works, for example an .overlay(Material.ultraThin) blur on the loaded image.

Full Phase Control

The phase initializer exposes every loading state through the AsyncImagePhase enum: empty, success(Image), and failure(Error). Pass a Transaction to animate phase changes; the default transaction has no animation.

AsyncImage(
  url: url,
  transaction: Transaction(animation: .easeInOut)
) { phase in
  switch phase {
  case .empty:
    ProgressView()
  case .success(let image):
    image
      .resizable()
      .transition(.scale(scale: 0.1, anchor: .center))
  case .failure:
    Image(systemName: "wifi.slash")
  @unknown default:
    EmptyView()
  }
}
.frame(width: 44, height: 44)
.clipShape(Circle())

Handle @unknown default, because the enum can grow new phases. Without the custom transaction the image pops in with no transition.

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-swiftui-guide({ topic: "swiftui-mastering-asyncimage" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems