How To Use SwiftUI ProgressView To Show Download Progress
Daily Coding Tip 016

ProgressView was introduced at WWDC20, and it’s very similar to UIProgressView, which is often used in combination with WKWebView when a webpage is loading.
In this example, we’re going to use ProgressView to show the progress of a real download that is not mocked. This way you can see how it’s used in a real example, and not seeing it go up according to a timer (which I was very tempted to do instead).
This code is based on Tracking Download Progress by Christopher Webb, a UIKit example that used a UIProgressView instead of SwiftUI.
The Download class
First we’ll create a class that will represent a single download.
The download needs a delegate that will inform it when the download progress needs to be updated, as well as the URL it is downloading from and the task that does it. When the progress reaches 1, the reference to the downloadTask is removed. I’ve also included the protocol DownloadDelegate, as this defines what we require from the ObservableObject that will perform that role.

Next we need an ObservableObject that will store the data that our view will rely on. There are three Published properties that will update the SwiftUI when they change. ProgressView can be given a string that will be displayed above it, and obviously the progress determines how much the bar should be filled in. The DataModel needs to inherit from NSObject first, because this is the only way that it will be able to be able to inherit from the delegates.

Now the real work begins, to make the DataModel class two kinds of delegate: the already defined DownloadDelegate, and Apple Foundation’s URLSessionDownloadDelegate. Let’s start with DownloadDelegate, which requires one function to commence the download, and another to handle updating the progress. You might notice that I’m using a long URL from Wikimedia. I went on the category for large images, and chose the first one alphabetically. This is Almeida Júnior - Saudade (Longing), a painting by Jose Ferraz de Almeida Júnior.
The image is roughly 2 metres by 1 metre in size, and I chose it so that the download would be slow enough to be visible on the ProgressView.

The download is created using the URL, and has its delegate set to the DataModel. Finally the downloadTask is set, and is given the command to start.
That’s what resume() means, despite its confusing name.
Now we’re going to use URLSessionDownloadDelegate to specify what should happen in two situations. When the progress updates, we want to update our Published property for progress, which will in turn update the ProgressView. When the download finishes, we use the data at the temporary location to construct a UIImage.

As the UIImage is also a Published property, it will be displayed as soon as it is no longer nil. You may have to wait a few seconds though, as your device or Simulator will need to compress the image to the resolution that it will be displayed at. When the image hasn’t loaded yet a Spacer will be shown instead, otherwise the ProgressView and Button will be in the centre initially and suddenly move to the bottom when the image loads.
When the download progress reaches 1, we will momentarily show a ProgressView that uses a CircularProgressViewStyle. You’ll notice that this ProgressView has no value, as it displays a spinner instead of a progress bar. This spinner will then be replaced by the image when the data has been converted to UIImage.

If everything has gone according to plan, you should have a progress bar and a Button that starts the download.
When the download has completed, you should see the image above.
Sorry I missed my usual daily deadline today, it’s just past midnight in my time zone.
In many US time zones it’s still Sunday, so this still counts as daily?!

