Posted on
Reading Time: 6 minutes

Hey there reader, hope you’re enjoying the series. If you haven’t already read parts 1 and 2 (an overview of progress indicators and a dive into indeterminate progress indicators, respectively), I recommend checking out those entries. If you are only interested in determinate indicators today, this post will be enough to get you moving.

In contrast to indeterminate indicators, which do not specify the duration of a delay and act as a simple indication that the system is working, determinate indicators do supply the user with a measurement of progress. The key feature of a determinate progress indicator is its presentation of the estimated time remaining or percent of completion. This estimate ensures that the user is not left in the dark.

Ah yes, who doesn’t remember that hit album Songs of the Puffin. Image credit: Apple.

Once more referencing Jakob Nielsen’s benchmarks for system response times: a user will not wait longer than 10 seconds before shifting focus to a new task. That’s a fair assumption. I wouldn’t sit doing nothing for 10 seconds hoping for a system response. When the user initiates a process that is known to take a long time, the system should provide feedback indicating how long the process will take. If the user is not granted a time estimate it is likely that phones will be thrown across the room, resulting in the injury of children or small animals. At minimum, allowing a process to endure for longer than 10 seconds without feedback may result in frustration, boredom, or a loss of trust in the system.

I’ve said it before: when in doubt, always use native UIKit components. Just as there is a native indeterminate spinner (UIActivityIndicatorView), there is also a native determinate progress indicator: UIProgressView. This component comes with a default VoiceOver announcement: when focused, it announces the progress in percent. The Accessibility Label of the UIProgressView may be set to a relevant descriptive message, such as “Uploading,” and VoiceOver will append the progress value to that message automatically. In this example, VoiceOver would announce, “Uploading, progress 42 percent.” That’s pretty helpful.

As a note, there is no need to listen for the Reduce Motion setting when using the native progress bar, because it does not contain any spinning or other rapidly changing animation. Unless you are building a highly intricate, lively and exhilarating custom progress bar with recursive motion, this type of visual feedback should not trigger a negative vestibular response.

Unlike the indeterminate progress indicator covered in the last article, it is simple to implement a determinate progress bar. There is no question of whether a it should be parallel or obstructive, because a system should never obstruct interaction with the system for longer than 10 seconds. Any process that endures for this amount of time must run quietly in the background and allow the user to interact with different elements on screen, navigate to a different screen, or even leave the app.

If there is a case in which a determinate progress indicator must take over the whole system, such as saving data or updating a view, then the indicator may appear in an overlay or dialog to prevent user interference. However, the overlay or dialog must have an exit route, such as a Close or Cancel button. In no way should users be suspended in purgatory when they could be doing other things.

Updating Progress

In order to prevent the perception that a process has stalled, the progress bar should update its visual progress frequently, if not continuously. A UIProgressView will not do this on its own, so this means programmatically updating its progress value. If the process serves up data on its progress, this can be used to calculate a progress value. If, on the other hand, the process is time-based or progress is spoofed for the user’s benefit (as in a demo app like the one at the end of this article), then it can be updated using a Timer object.

VoiceOver Updates

If VoiceOver focus is set on the progress bar, then it will announce the progress at regular intervals of a couple seconds. It is unlikely, however, that a VoiceOver user will just leave his or her focus sitting idly on the progress bar–so how should the system keep the user up to date?

When you first learn about the UIAccessibility.post(notification:argument:) as a developer, it may be tempting to cram VoiceOver notifications into every user interaction. In the case of a determinate progress indicator, however, the process runs in parallel to user interaction so that the user can be freed from having to watch the progress bar as it slowly groans to completion. If the user wants to check in on the progress, she can simply move VoiceOver focus back to the progress bar at will. Therefore a VoiceOver user should not be pummeled with unsolicited updates every couple of seconds (“22 percent….23 percent….23 and one-sixteenth percent…”) as this would only interrupt the user’s experience. Leave that option to the user.

Although it is not a good idea to barrage the user with VoiceOver announcements, the user should be notified when a process completes. The only downside to being freed to navigate elsewhere within the application while a process runs in parallel is that the user will not be made aware of process completion until they check back in with the progress bar. This could be a serious point of frustration if the user is left waiting when the process has already finished. For this purpose, make a notification with UIAccessibility.post(notification:argument:) at completion:

if progressBar.progress >= 1.0 {
  UIAccessibility.post(notification: UIAccessibility.Notification.announcement, argument: "Download complete")
}

If a process is going to take a long time, the reason for the delay should be communicated clearly to the user. Typically determinate processes are user-initiated, but it may still be helpful to affirm what is going on, especially if the process is going to take a very long time or consists of multiple phases: for example, “Uploading photos,” or “Downloading 21 of 23 messages.” The best way to keep a user informed, though, is to entertain them at the same time. Consider the incredible, semi-nonsensical SimCity 4 loading messages that kept us interested while we waited to sow the seeds of our domain, messages like “Reticulating Splines,” “Bureacritizing bureaucracies,” and “Deciding what message to display next.”

Custom VoiceOver Units

By default VoiceOver announces the progress value of a UIProgressView in units of percent. There may be a localized case, however, in which human-readable units may be more useful. Let’s take a video player application as our example: knowing that I am 64% through episode 1 of Tiger King doesn’t help if I want to know how much time is left in the episode, or how to find my place if I return to the show, or whether Carole Baskin killed her husband. For the first two problems, at least, the output should be displayed in hours, minutes and seconds.

This type of custom accessibility element should be used only if the more specific unit announcement does not cause a loss of information or context. For example, in a video player, my progress in percent really does not give me much to work with, so replacing those units with hours, minutes and seconds does not result in a loss of information. In the case of a file download, on the other hand, a user may find more utility in knowing the percent complete than the number of MB downloaded so far.

Summary

  • Determinate progress indicators do provide the progress in percent and/or a time estimate of process completion; they should be used for delays lasting 10 seconds or longer
  • In UIKit, the UIProgressView serves as the native determinate progress indicator
  • A determinate progress indicator should never prevent simultaneous user interaction with other parts of the screen or application
  • VoiceOver should not make periodic updates on progress (unless focus is set to the progress bar)
  • VoiceOver should announce when the process has completed
  • The user should be kept informed of what the system is up to: provide a descriptive message so the user understands what is happening

Previous article (Part 2 of 3) : Indeterminate progress indicators (spinners)

Code Example

iOS 13.4 – Swift 5 – Xcode 11.4

The best way to understand ideal behavior is to try it yourself. Try opening the repo below on your iPhone or iPad and testing the demo with VoiceOver.

Determinate Progress Indicator – GitHub repo

A mockup of the iOS Software Update screen with a progress bar at 24%
Screenshot of a demo app with a determinate progress indicator

Resources