
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?!