SwiftUI Progress View – Showing Time Progress

Posted in SwiftUI

August 19th, 2022

⏱ Reading Time: 4 mins

The progress view, also known as activity indicator or spinner in UIKit, is a SwiftUI view that indicates visually the progress of a task towards its completion. It has two variations; linear and circular, where the first represents the progress of tasks that we can determine when they finish, while the second shows progress of tasks with unpredictable completion point.

There is already an older post where I’m discussing about the progress view, and if you just want to get the grasp of it, please take a few minutes to give it a read. In this post, the aim is to present a new capability of progress view that was introduced in the fourth revision of SwiftUI, and it’s still in beta stage at the time of writing this.

To be more particular, progress view in SwiftUI is now capable of displaying time progress based on a given time interval that we provide it with. This kind of progress reporting is suitable when counting time and we want to also show a visual representation of that, such as in a count down. But as it happens with many new features, this addition is unfortunately available only in systems presented in WWDC 2022 and above; iOS 16+, macOS 13+, and so on.

Implementing a progress view with time interval

In its simplest form, a progress view that displays time based progress is initialized with one required argument only; the time interval that will be animating the progress for.

There is an important fact to take into consideration here. That time interval is not a TimeInterval (aka Double) value, but a Date closed range like this:

The approach that I find as the most handy one is to prepare this closed range beforehand, and then pass it as argument to the progress view at the initialization point.

With that in mind and in order to demonstrate some code, let’s declare a computed property in the SwiftUI view struct that will be returning a Date closed range similar to the previous example. In its body we’ll specify two dates; the start and end dates, with the second representing a timestamp five (5) seconds after the first one. As a last step, we will form a close range using these two dates and we’ll return them from the property.

Here is what I just described in code:

Two things to note here. Firstly, there are more ways to initialize the start and end date instances, but I believe the above is the fastest one for this particular example. Secondly, it’s not necessary to create a computed property when it comes to use a time based progress view. It could also be a function accepting values that would be forming the dates dynamically. Or even faster, to build the closed range inline right when the progress view is initialized. However, for clarity and for the sake of the demonstration, the above is what we’ll stick with here.

With the progressInterval in place, let’s initialize a progress view in the simplest possible way:

See that the progressInterval is supplied as argument to the ProgressView(timeInterval:) initializer. This is enough if we don’t want to apply any styling with view modifiers; the padding shown above is to prevent the progress view from sticking to the screen edges in the following figure, which we get by running the demo app with the above code:

You can notice from the above demonstration that the progress is going towards the empty state instead of getting filled, however that’s the default behavior. To change it and make the progress keep filling the view, it’s necessary to provide an additional, optional argument to the initializer, setting the false value to it:

Besides the progress bar’s direction, you can also notice in the two previous graphics that there is a built-in label reporting the time, with the displayed value also depending on the countsDown parameter value.

On top of all that, there is one more optional argument that we can provide; a custom label that will be displayed right above the progress view:

The above is all it takes in order to show a progress view that will be reporting progress for a specific date range. Keep in mind that all the above work for the linear progress view style; applying a circular style will have no result and the spinner will be animating forever.

Time based progress view with custom styles

As it turns out, this particular kind of progress view can accept a custom style if only a ProgressView instance is initialized in the custom style’s implementation. Let’s use as example the following custom style implementation taken from the previous post about the progress view:

Now let’s apply it to the progress view:

As you will see next, the result is the expected one:

Notice that in the above custom style there is a ProgressView instance that gets initialized in the makeBody(configuration:) method. The following, however, is not going to work; also taken from the previous post, that custom style does not create a new ProgressView instance, but it represents progress by combining other views and using data from the configuration parameter value:

As it seems, the fractionCompleted property of the Configuration type does not get updated when using a date range with the progress view, so no progress is indicated here. That’s useful information to know in advance, so you avoid implementing custom styles that most probably won’t lead to the desired result.


In my opinion, the time based progress view is a useful feature that was definitely missing from the various flavors of the progress view. However, I find two negative points, with the first being the lack of support of previous system versions. The other one is the incapability to create fully custom styles that would fit to the appearance of custom interfaces. Regardless, even with none or a slight customization, this new kind of progress view remains quite interesting and useful.

Thanks for reading, take care! ????


Progress View in SwiftUI

Stay Up To Date

Subscribe to my newsletter and get notifiied instantly when I post something new on SerialCoder.dev.

    We respect your privacy. Unsubscribe at any time.