SwiftUI · · 6 min read

Working with ProgressView and ProgressViewStyle in SwiftUI

Working with ProgressView and ProgressViewStyle in SwiftUI

Progress bars or other kinds of indicators play a vital role in apps that execute time-consuming tasks or operations. It enriches the user experience by displaying a progress indicator, which enables the user to track the progress of the operation and get an idea of how long it will take to complete.

In SwiftUI, it comes with a built-in component called ProgressView for developers to present a progress bar or a circular indicator to display the progress of a long-running operation, such as downloading a file or uploading data to a server.

The default appearance of ProgressView is simple and minimalistic, and it might not match the design and theme of your app. This is where the ProgressViewStyle protocol comes into play. You can use the ProgressViewStyle protocol to create custom styles for the ProgressView component that match the design and theme of your app.

In this tutorial, I’ll walk you through the basics of ProgressView and show you how to customize the look & feel of the progress view using ProgressViewStyle.

The Basic Implementation of ProgressView

swiftui-progress-view-circular-indicator

The ProgressView component is very easily to use. In your SwiftUI apps, you can create a progress indicator by writing the following line of code:

ProgressView()

Optionally, you can add a label to display the status message like this:

ProgressView() {
    Text("Loading...")
}

On iOS, the progress view is displayed as a circular indicator.

Indeterminate Progress vs Determineate Progress

swiftui-progress-bar

The ProgressView component provides two types of progress indicator to indicate indeterminate progress and determineate progress. What we just demonstrated in the previous section is an indeterminate progress indicator. For tasks that have an unknown duration, such as loading or syncing complex data, we use indeterminate progress indicators, which is displayed as a circular activity indicator.

When possible, use a determinate progress indicator. An indeterminate progress indicator shows that a process is occurring, but it doesn’t help people estimate how long a task will take. A determinate progress indicator can help people decide whether to do something else while waiting for the task to complete, restart the task at a different time, or abandon the task.

Apple’s Human Interface Guidelines

For a determinate progress indicator, it’s used for tasks with a well-defined duration such as file downloads. On iOS, a determinate progress indicator is presented in the form of a progress bar. To implement it, you just need to instantiate a ProgressView with the current value of the progress. Here is an example:

ProgressView(value: 0.3)

By default, the total value is 1.0. You can provide a value between 0.0 and 1.0 where 1.0 means 100% complete. If you have to use a different range with a different total, you can write the code by using another initializer:

ProgressView(value: 30, total: 100)

Both lines of the codes present the same progress indicator that shows 30% complete.

swiftui-progressview-label

Optionally, you can add some labels to display the status message and current progress by using the label and currentValueLabel parameters:

ProgressView(value: 0.3,
             label: { Text("Processing...") },
             currentValueLabel: { Text("30%") })

In order to update the value of the progress, you usually create a state variable to hold the current value of the progress. Here is a sample code snippet:

struct ContentView: View {

    @State private var progress = 0.1

    var body: some View {
        ProgressView(value: progress,
                     label: { Text("Processing...") },
                     currentValueLabel: { Text(progress.formatted(.percent.precision(.fractionLength(0)))) })
            .padding()
            .task {
                Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in

                    self.progress += 0.1

                    if self.progress > 1.0 {
                        self.progress = 0.0
                    }
                }
            }
    }
}

Changing the Progress View’s Color and Style

swiftui-progress-bar-color

The default progress indicator has a simple design with limited customization options. However, it is possible to modify the color of the progress bar by applying the tint modifier:

ProgressView(value: 0.3)
    .tint(.purple)

A determinate progress indicator is typically displayed as a progress bar. If you wish to modify its appearance, you can use the progressViewStyle modifier. For instance, you can switch from the default linear style to a circular style, like so:

ProgressView(value: 0.3, label: { Text("Processing...") }, currentValueLabel: { Text("30%") })
    .progressViewStyle(.circular)

This transforms the progress bar into a circular indicator.

swiftui-circular-indicator-style

Using ProgressViewStyle for Advanced Customizations

Although the default style of the progress view is sufficient for most applications, you may want to create unique and visually appealing progress bars that match their app’s design and theme. If you want to create a customized progress bar that aligns with your app’s aesthetic, you can use the ProgressViewStyle protocol in SwiftUI.

ProgressViewStyle is a powerful tool for creating customized progress indicators in SwiftUI. With ProgressViewStyle, you can create unique and visually appealing progress bars that perfectly match the design and theme of your app. There are several built-in styles available, such as CircularProgressViewStyle and LinearProgressViewStyle. By conforming to the ProgressViewStyle protocol, you can create your own styles and apply them to the ProgressView component. This allows for advanced customizations that go beyond changing the color of the progress bar.

swiftui-custom-progress-bar

To give you a better idea about ProgressViewStyle, let’s build a custom progress bar like the one shown above. What you need to do is to create a new struct that conforms to the ProgressViewStyle protocol and implement the following required method:

@ViewBuilder func makeBody(configuration: Self.Configuration) -> Self.Body

If you have experience with ButtonStyle, the implementation of ProgressViewStyle is very similar as it follows the same pattern.

Let’s create a new style named BarProgressStyle like this:

struct BarProgressStyle: ProgressViewStyle {

    var color: Color = .purple
    var height: Double = 20.0
    var labelFontStyle: Font = .body

    func makeBody(configuration: Configuration) -> some View {

        let progress = configuration.fractionCompleted ?? 0.0

        GeometryReader { geometry in

            VStack(alignment: .leading) {

                configuration.label
                    .font(labelFontStyle)

                RoundedRectangle(cornerRadius: 10.0)
                    .fill(Color(uiColor: .systemGray5))
                    .frame(height: height)
                    .frame(width: geometry.size.width)
                    .overlay(alignment: .leading) {
                        RoundedRectangle(cornerRadius: 10.0)
                            .fill(color)
                            .frame(width: geometry.size.width * progress)
                            .overlay {
                                if let currentValueLabel = configuration.currentValueLabel {

                                    currentValueLabel
                                        .font(.headline)
                                        .foregroundColor(.white)
                                }
                            }
                    }

            }

        }
    }
}

This custom style takes in three parameters for configuring the bar color, height, and font style. I’m not going to explain the code in the makeBody method line by line. In brief, we implement our own progress bar style by overlaying a purple RoundedRectangle view on top of another RoundedRectangle view in light gray.

The configuration parameter provides you with the common properties of a progress view including:

  • fractionCompleted – The completed fraction of the task represented by the progress view, from 0.0 (not yet started) to 1.0 (fully complete).
  • label – The status label.
  • currentValueLabel – the label that shows the current progress.

Now that we’ve created our own style, you can apply it by attaching the progressViewStyle modifier to the progress view:

ProgressView(value: 0.3, label: { Text("Processing...") }, currentValueLabel: { Text("30%") })
    .progressViewStyle(BarProgressStyle(height: 100.0))

Exercise

After gaining an understanding of ProgressViewStyle, it’s time to put that knowledge into practice and create a new custom style. You can challenge yourself by attempting to build a style that resembles the one shown in the image below. This will not only help you reinforce what you have learned but also push you to think creatively about how to achieve your desired look and feel. Don’t be afraid to experiment and have fun with it!

swiftui-progress-style-exercise

Summary

Throughout this tutorial, we have covered the fundamentals of using ProgressView to display the progress of time-consuming tasks or operations in your app. It is recommended to use a determinate progress indicator whenever possible, as it provides users with a more accurate understanding of the progress being made.

While the default progress view is adequate for most applications, we have also explored how to create a customized progress view using the ProgressViewStyle protocol. By following the steps outlined in this tutorial, you should now have a solid understanding of how to create your own unique style of progress view that aligns with the design and theme of your app.

Read next