History
Loading...
Loading...
September 9, 2025
AsyncImage with multiple placeholder states to handle loading, success, and error cases. Wrap it in a frame modifier to prevent layout shifts: AsyncImage(url: url) { image in image.resizable() } placeholder: { ProgressView() }.frame(width: 200, height: 200)Always handle all three phases of AsyncImage (empty, success, failure) with consistent frame sizing to prevent layout jumps during image loading states.
struct RemoteImageView: View {
let url: URL?
let size: CGSize
var body: some View {
AsyncImage(url: url) { phase in
switch phase {
case .empty:
ProgressView()
.frame(width: size.width, height: size.height)
case .success(let image):
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: size.width, height: size.height)
.clipped()
case .failure(_):
Image(systemName: "photo")
.foregroundColor(.gray)
.frame(width: size.width, height: size.height)
.background(Color.gray.opacity(0.2))
@unknown default:
EmptyView()
}
}
}
}This approach provides a smooth user experience by maintaining consistent layout dimensions, shows appropriate feedback for each loading state, and gracefully handles network failures without breaking the UI flow.