SwiftUI · · 5 min read

How to Use SwiftUI Gauge and Create Custom Gauge Styles in iOS 16

How to Use SwiftUI Gauge and Create Custom Gauge Styles in iOS 16

In iOS 16, SwiftUI introduces a new view called Gauge for displaying progress. You can actually use it to show values within a range. In this tutorial, let’s see how to use the Gauge view and work with different gauge styles.

A gauge is a view that shows a current level of a value in relation to a specified finite capacity, very much like a fuel gauge in an automobile. Gauge displays are configurable; they can show any combination of the gauge’s current value, the range the gauge can display, and a label describing the purpose of the gauge itself.

– Apple’s official documentation

The simplest way to use Gauge is like this:

struct ContentView: View {
    @State private var progress = 0.5

    var body: some View {
        Gauge(value: progress) {
            Text("Upload Status")
        }
    }
}

In the most basic form, a gauge has a default range from 0 to 1. If we set the value parameter to 0.5, SwiftUI renders a progress bar indicating the task is 50% complete.

swiftui-gauge-basic

Optionally, you can provide labels for the current, minimum, and maximum values:

Gauge(value: progress) {
    Text("Upload Status")
} currentValueLabel: {
    Text(progress.formatted(.percent))
} minimumValueLabel: {
    Text(0.formatted(.percent))
} maximumValueLabel: {
    Text(100.formatted(.percent))
}

Using Custom Range

The default range is set to 0 and 1. That said, you can provide your custom range. For example, you are building a speedometer with the maximum speed of 200km/h. You can specify the range in the in parameter:

struct SpeedometerView: View {
    @State private var currentSpeed = 100.0

    var body: some View {
        Gauge(value: currentSpeed, in: 0...200) {
            Text("Speed")
        } currentValueLabel: {
            Text("\(currentSpeed.formatted(.number))km/h")
        } minimumValueLabel: {
            Text(0.formatted(.number))
        } maximumValueLabel: {
            Text(200.formatted(.number))
        }
    }
}

In the code above, we set the range to 0...200. If you already add the SpeedometerView in the preview struct. Your preview should fill half of the progress bar as we set the current speed to 100km/h.

swiftui-gauge-speedometer

Using Image Labels

You are not limited to use text labels for displaying ranges and current value. Here is an example:

Gauge(value: currentSpeed, in: 0...200) {
    Image(systemName: "gauge.medium")
        .font(.system(size: 50.0))
} currentValueLabel: {
    HStack {
        Image(systemName: "gauge.high")
        Text("\(currentSpeed.formatted(.number))km/h")
    }
} minimumValueLabel: {
    Text(0.formatted(.number))
} maximumValueLabel: {
    Text(200.formatted(.number))
}

We change the text label of the gauge to a system image. And, for the current value label, we create a stack to arrange the image and text. Your preview should display the gauge like that shown in figure 3.

swiftui-gauge-medium-image

Customizing the Gauge Style

customise-gauge-color

The default color of the Gauge view is blue. To customize its color, attach the tint modifier and set the value to your preferred color like this:

Gauge(value: currentSpeed, in: 0...200) {
    Image(systemName: "gauge.medium")
        .font(.system(size: 50.0))
} currentValueLabel: {
    HStack {
        Image(systemName: "gauge.high")
        Text("\(currentSpeed.formatted(.number))km/h")
    }
} minimumValueLabel: {
    Text(0.formatted(.number))
} maximumValueLabel: {
    Text(200.formatted(.number))
}
.tint(.purple)

The look & feel of the Gauge view is very similar to that of ProgressView. Optionally, you can customize the Gauge view using the gaugeStyle modifier. The modifier supports several built-in styles.

linearCapacity

This is the default style that displays a bar that fills from leading to trailing edges. Figure 4 shows a sample gauge in this style.

accessoryLinear

This style displays a bar with a point marker to indicate the current value.

swiftui-gauge-current-value

accessoryLinearCapacity

For this style, the gauge is still displayed as a progress bar but it’s more compact.

swiftui-accessorylinearcapacity-style

accessoryCircular

Instead of displaying a bar, this style displays an open ring with a point marker to indicate the current value.

swiftui-accessorycircular-gauge-style

accessoryCircularCapacity

This style displays a closed ring that’s partially filled in to indicate the gauge’s current value. The current value is also displayed at the center of the gauge.

swiftui-accessorycircularcapacity-gauge-style

Creating a Custom Gauge Style

custom-swiftui-gauge-style

The built-in gauge styles are limited but SwiftUI allows you to create your own gauge style. Let me show you a quick demo to build a gauge style like the one displayed in figure 9.

To create a custom gauge style, you have to adopt the GaugeStyle protocol and provide your own implementation. Here is our implementation of the custom style:

struct SpeedometerGaugeStyle: GaugeStyle {
    private var purpleGradient = LinearGradient(gradient: Gradient(colors: [ Color(red: 207/255, green: 150/255, blue: 207/255), Color(red: 107/255, green: 116/255, blue: 179/255) ]), startPoint: .trailing, endPoint: .leading)

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

            Circle()
                .foregroundColor(Color(.systemGray6))

            Circle()
                .trim(from: 0, to: 0.75 * configuration.value)
                .stroke(purpleGradient, lineWidth: 20)
                .rotationEffect(.degrees(135))

            Circle()
                .trim(from: 0, to: 0.75)
                .stroke(Color.black, style: StrokeStyle(lineWidth: 10, lineCap: .butt, lineJoin: .round, dash: [1, 34], dashPhase: 0.0))
                .rotationEffect(.degrees(135))

            VStack {
                configuration.currentValueLabel
                    .font(.system(size: 80, weight: .bold, design: .rounded))
                    .foregroundColor(.gray)
                Text("KM/H")
                    .font(.system(.body, design: .rounded))
                    .bold()
                    .foregroundColor(.gray)
            }

        }
        .frame(width: 300, height: 300)

    }

}

In order to conform the protocol, we have to implement the makeBody method to present our own gauge style. The configuration bundles the current value and value label of the gauge. In the code above, we use these two values to display the current speed and compute the arc length.

Once we implement our custom gauge style, we can apply it by attaching the gaugeStyle modifier like this:

struct CustomGaugeView: View {

    @State private var currentSpeed = 140.0

    var body: some View {
        Gauge(value: currentSpeed, in: 0...200) {
            Image(systemName: "gauge.medium")
                .font(.system(size: 50.0))
        } currentValueLabel: {
            Text("\(currentSpeed.formatted(.number))")

        } 
        .gaugeStyle(SpeedometerGaugeStyle())

    }
}

I created a separate view for the demo. To preview the CustomGaugeView, you need to update the ContentView_Previews struct to include the CustomGaugeView:

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
        SpeedometerView()
            .previewDisplayName("Speedometer")

        CustomGaugeView()
            .previewDisplayName("CustomGaugeView")
    }
}

That’s it. If you’ve made the changes, your preview should show a custom gauge.

swiftui-gauge-purple-speedometer

Read next