When it comes to building iOS apps, efficiently loading and displaying images from a URL is a common task. In SwiftUI, Apple has introduced the AsyncImage view, which simplifies this process and provides a seamless experience for users.
In this tutorial, we will explore how to leverage the power of AsyncImage to effortlessly load and display images from a URL in SwiftUI. It’s important to note that AsyncImage is available starting from iOS 15 and higher.
Before we dive into the implementation details, it’s worth mentioning that sizing images in SwiftUI can sometimes be a challenge. If you’re interested in learning more about this topic, I recommend checking out our blog post on Sizing SwiftUI Image View.
Now, let’s get started with loading and displaying images from a URL using the AsyncImage view in SwiftUI.
How to Load an Image from a URL with AsyncImage
In SwiftUI, loading and displaying images from a URL is made easier with the AsyncImage view. It allows you to asynchronously load images from a remote location and handle the state of the image loading process.
The most basic use of AsyncImage to load images from a URL is:
AsyncImage(url: URL(string: "https://picsum.photos/id/12/200"))
In this example, we use the AsyncImage initializer that takes a URL to handle the downloaded image. We provide a URL to the Lorem Picsum API, which uses a dummy image with a size of 200 by 200 pixels. SwiftUI will use 200 by 200 points to display the image, which makes it look very blurry on a high resolution iPhone (with a 3x resolution). In the following you can see the comparison of a 1x and 3x image
You can set the scale of AsyncImage to 3 and download a 3 times larger image (600 by 600 pixel) image to show a sharp image:
AsyncImage(url: URL(string: "https://picsum.photos/id/12/600"),
scale: 3)
You can get the scale value from the environment. This is for the new iPhones 3 and for iPad and Mac 2:
struct ContentView: View {
@Environment(\.displayScale) var scale
var body: some View {
AsyncImage(url: URL(string: "https://picsum.photos/id/12/600"),
scale: scale)
}
}
How to Resize Images with AsyncImage
AsyncImage just like the SwiftUI image will use the size of the image per default. You need to use the resizable modifier, but this does not work directly on AsyncImage. Instead, I have to use a different initializer where I get the image view in a closure:
AsyncImage(url: URL(string: "https://picsum.photos/id/12/600")) { image in
image.resizable()
} placeholder: {
ProgressView()
}
.frame(width: 200, height: 200)
The closure receives the downloaded image as a parameter and we can apply any necessary modifications, such as making it resizable and setting its frame. I am using a 600 by 600 pixel image and set its frame to 200 by 200 points.
I am also adding a placeholder that is shown during the loading. In the above example, a loading indicator is shown. I am adding the frame around the AsyncImag because I want the same size for the placeholder and image.
Handling Placeholder and Error States
AsyncImage provides additional functionality for handling placeholder and error states. We can specify a placeholder view to be displayed while the image is being loaded, and we can handle loading errors using the image phase of the AsyncImage initializer.
struct ContentView: View {
let baseURLString = "https://picsum.photos/id/12/"
@Environment(\.displayScale) var scale
let size: CGFloat = 200
var urlString: String {
baseURLString + "\(Int(size * scale))"
}
var body: some View {
AsyncImage(url: URL(string: urlString),
scale: 3,) { phase in
switch phase {
case .empty:
ZStack {
Color.gray
ProgressView()
}
case .success(let image):
image.resizable()
case .failure(let error):
Text(error.localizedDescription)
// use placeholder for production app
@unknown default:
EmptyView()
}
}
.frame(width: size, height: size)
}
}
In this updated example, we handle different phases of the image-loading process using a switch statement. We display a ProgressView as a placeholder while the image is being loaded, show the downloaded image on success, and display an error message on failure. We also handle any unknown cases with an EmptyView.
The frame is added around the AsyncImage. This assures that the view has in all states the same size. This is important when you want to lazily load images in a list or scroll view and want to ensure that the scrolling is smooth and without jumps.
Why Showing Placeholders is Important
Showing a placeholder image is important for a few reasons:
- Visual Feedback: When an image is being loaded from a URL, it takes some time for the data to be fetched and displayed. During this loading period, displaying a placeholder image provides visual feedback to the user that something is happening and that the image will appear shortly. This helps to prevent a blank or empty space from appearing, which can be confusing or give the impression of a broken feature.
- User Experience: Placeholder images contribute to a smoother and more seamless user experience. They give the user a sense of progress and make the app feel more responsive, even when the image loading process takes longer. This can help to retain user engagement and prevent frustration.
- Error Handling: In situations where the image loading fails due to network issues or invalid URLs, a placeholder image can serve as a fallback option. Instead of leaving a broken or missing image, the placeholder image ensures that there is still some visual representation in place. This helps to maintain the overall layout and design of the app, even in the absence of the intended image.
Overall, showing a placeholder image is a good practice to provide visual feedback, enhance user experience, and handle potential errors gracefully. It ensures a more polished and professional look for your app, even during the image loading process.
Loading Mechanism
SwiftUI will take care of loading the image asynchronously using the provided URL. It manages the download and caching of the image, so you don’t have to write custom networking code for image loading. If the view disappears before the download is complete, the URL request is canceled.
For example, if you use AsyncImage in a ScrollView and scroll very fast, some of the downloads will be canceled. Unfortunately, AsyncImage does not restart the process when the list cell reappears:
Some people have reported problems with the caching. Therefore, I would recommend writing your own loading handler, if you have a more advanced and complex use case.
Adding Animations to AsyncImage
You can use the transaction parameter of AsyncImage to add an animation when the image is shown. This will add a fade in animation:
AsyncImage(url: …,
transaction: .init(animation: .bouncy(duration: 1))) { phase in
…
}
Conclusion
In this blog post, we explored how to efficiently load and display images from a URL in SwiftUI using the AsyncImage view. We discussed the importance of handling image loading to improve app performance and user experience. Using code snippets and examples, we covered topics such as loading images from a URL, displaying the loaded image with customizable options, and implementing image caching for better performance. We also delved into error handling and the usage of placeholder images. Additionally, we emphasized the significance of testing image loading functionality. By the end of this tutorial, readers will have a solid understanding of how to leverage AsyncImage to efficiently handle image loading in their SwiftUI apps.
Further Reading:
- Sizing SwiftUI Image View
- See examples for image collections views with ScrollView: How to use SwiftUI ScrollView