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.