TechnologiesSwiftUI

AsyncImagePhase enum

iOSmacOStvOSwatchOSvisionOSiOS 15.0+✓ renders

The current phase of the asynchronous image loading operation.

How it works

AsyncImagePhase represents the current state of an image that AsyncImage loads over the network. Because that download happens asynchronously, the view passes through a sequence of phases — no image yet, a loaded image, or a failure — and AsyncImagePhase is the value SwiftUI hands your content closure so you can render the right thing for each one. Reach for it whenever you use the closure-based AsyncImage initializer and want full control over the loading placeholder, the resolved image, and the error state, rather than accepting the default behavior.

  1. Receive the phase in the AsyncImage content closure

    The trailing-closure form of AsyncImage calls your closure each time loading progresses, passing one AsyncImagePhase value. In the example the parameter phase is that value, and the whole closure body re-runs as the phase advances from empty to success or failure.

  2. Switch over the three cases

    AsyncImagePhase is an enum, so you handle it with a switch. Its cases are .empty (no image has loaded yet, typically while the request is in flight), .success (the image resolved), and .failure (the load errored). Each branch returns the view to show for that state.

  3. Unwrap the loaded image from .success

    The .success case carries an associated Image value, bound here with case .success(let image). That image is a ready-to-use SwiftUI Image, so you apply normal modifiers to it — the example chains .resizable() and .scaledToFit() to size the photo within its frame.

  4. Show a placeholder for .empty and a fallback for .failure

    While the phase is .empty, render a stand-in such as the ProgressView() shown here; when it is .failure, render an error fallback like the Image(systemName: "photo") styled with .font(.largeTitle) and .foregroundStyle(.secondary). This is where AsyncImagePhase earns its keep — it lets you design loading and error states yourself.

  5. Handle future cases with @unknown default

    AsyncImagePhase is a non-frozen enum, so SwiftUI may add cases in later releases. Covering them with @unknown default — returning EmptyView() in the example — keeps the switch exhaustive today while staying forward-compatible.

Try it — Point the AsyncImage url at a string that doesn't resolve to an image to force the .failure branch and watch the Image(systemName: "photo") fallback appear instead of the photo.

Example & preview

Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.

AsyncImagePhase.swift
struct AsyncImagePhaseDemo: View {
    var body: some View {
        AsyncImage(url: URL(string: "https://example.com/photo.jpg")) { phase in
            switch phase {
            case .empty:
                ProgressView()
            case .success(let image):
                image
                    .resizable()
                    .scaledToFit()
            case .failure:
                Image(systemName: "photo")
                    .font(.largeTitle)
                    .foregroundStyle(.secondary)
            @unknown default:
                EmptyView()
            }
        }
        .frame(width: 120, height: 120)
        .padding()
    }
}
Live preview
swift → lexer → parser → sema → uiir → canvas Open in Studio ↗
What's new in SwiftUI 27 →