New in SwiftUI 4: Gauge View

DevTechie Inc
Jun 24, 2022

Gauge is a newly introduced view in SwiftUI 4 and is used to show values within a range.

Gauge view has been present in Apple watch faces for long time and are used to display current level of values in relation to a specified finite capacity.

For example, to shows min, max and current temp on watch face, Apple uses gauge view like the one shown below:

Starting SwiftUI 4, Gauge view is available for iOS, iPadOS, macOS, and Mac Catalyst along with WatchOS.

Let’s start with an example to create Gauge view, we will create a Gauge with value and label. By default, Gauge uses system context to decide the look and feel for the Gauge.

struct ContentView: View {
    
    var body: some View {
        VStack(spacing: 20) {
            Text("DevTechie")
                .font(.largeTitle)
            
            Gauge(value: 0.2) {
                Image(systemName: "thermometer")
                    .font(.caption)
            }
        }.padding()
    }
}
GaugeStyle
iOS by default shows a linear progress bar for Gauge, but we can change that with GaugeStyle.

GaugeStyle has a few options:

We will pick accessoryCircular style:

struct ContentView: View {
    
    var body: some View {
        VStack(spacing: 20) {
            Text("DevTechie")
                .font(.largeTitle)
            
            Gauge(value: 0.2) {
                Image(systemName: "thermometer")
                    .font(.caption)
            }
            .gaugeStyle(.accessoryCircular)
        }.padding()
    }
Let’s try other options too:

.gaugeStyle(.accessoryCircularCapacity)
accessoryCircularCapacity creates this view which only has a shadow for the indicator but if you look at the dark mode version, you will find Gauge to appear just fine.

Let’s fix this for light mode as well with the help of .tint modifier.

struct ContentView: View {
    
    var body: some View {
        VStack(spacing: 20) {
            Text("DevTechie")
                .font(.largeTitle)
            
            Gauge(value: 0.2) {
                Image(systemName: "thermometer")
                    .font(.caption)
            }
            .gaugeStyle(.accessoryCircularCapacity)
            .tint(.orange)
        }.padding()
    }
}
With tint applied, both light and dark mode look good:

linearCapacity is the same as we have got for the iOS when we created Gauge without style:

.gaugeStyle(.linearCapacity)
accessoryLinear is a gauge style that displays bar with a marker that appears at a point along the bar to indicate current gauge’s value.

.gaugeStyle(.accessoryLinear)
accessoryLinearCapacity style fills the gauge from leading to trailing edges as the current value increases.

.gaugeStyle(.accessoryLinearCapacity)
automatic gauge style is context based style which allows system to choose the correct gauge style for the system its being rendered on.

.gaugeStyle(.automatic)
Gradient Tint
We can apply gradient to gauge tint as well to provide more visual clues to the user. For example, we will mimic a case of hot day, which will start off with nice temp and will climb up to be a hot summer day.

.tint(Gradient(colors: [.green, .yellow, .orange, .red, .pink]))
It’s great to have visual indicators but it would be even better to have some labels so let’s add more info into the gauge.

Minimum, Maximum and Current Value
Gauge supports range operator where we can define the min, max values for the view.

For this, we will add three state properties:

@State private var current = 66.0
@State private var minimum = 53.0
@State private var maximum = 99.0
Gauge takes current, minimum, and maximum value label parameters as well which are all view builder closures giving us an opportunity to provide our own custom views, so let’s add that along with range parameter to the gauge.

Gauge(value: current, in: minimum...maximum) {
    Image(systemName: "thermometer")
        .font(.caption)
} currentValueLabel: {
    Text("\(Int(current))")
} minimumValueLabel: {
    Text("\(Int(minimum))")
} maximumValueLabel: {
    Text("\(Int(maximum))")
}
Our complete code will look like this:

import SwiftUIstruct ContentView: View {
    
    @State private var current = 66.0
    @State private var minimum = 53.0
    @State private var maximum = 99.0
    
    var body: some View {
        VStack(spacing: 20) {
            Text("DevTechie")
                .font(.largeTitle)
            
            Gauge(value: current, in: minimum...maximum) {
                Image(systemName: "thermometer")
                    .font(.caption)
            } currentValueLabel: {
                Text("\(Int(current))")
            } minimumValueLabel: {
                Text("\(Int(minimum))")
            } maximumValueLabel: {
                Text("\(Int(maximum))")
            }
            .gaugeStyle(.accessoryCircular)
            .tint(Gradient(colors: [.green, .yellow, .orange, .red, .pink]))
        }.padding()
    }
}
Dynamic Gauge
Value passed in gauge view can be dynamic in nature. Let’s create a slider control to increase the value and see it in gauge view.

We will add a slider and use current, minimum and maximum state properties in our slider as well:

struct ContentView: View {
    
    @State private var current = 66.0
    @State private var minimum = 53.0
    @State private var maximum = 99.0
    
    var body: some View {
        VStack(spacing: 20) {
            Text("DevTechie")
                .font(.largeTitle)
            
            Gauge(value: current, in: minimum...maximum) {
                Image(systemName: "thermometer")
                    .font(.caption)
            } currentValueLabel: {
                Text("\(Int(current))")
            } minimumValueLabel: {
                Text("\(Int(minimum))")
            } maximumValueLabel: {
                Text("\(Int(maximum))")
            }
            .gaugeStyle(.accessoryCircular)
            .tint(Gradient(colors: [.green, .yellow, .orange, .red, .pink]))
            
            Slider(value: $current, in: minimum...maximum) {
                Text("Mimic weather")
            }
        }.padding()
    }
}
Let’s format our min and max labels to match the visual clues:

struct ContentView: View {
    
    @State private var current = 66.0
    @State private var minimum = 53.0
    @State private var maximum = 99.0
    
    var body: some View {
        VStack(spacing: 20) {
            Text("DevTechie")
                .font(.largeTitle)
            
            Gauge(value: current, in: minimum...maximum) {
                Image(systemName: "thermometer")
                    .font(.caption)
            } currentValueLabel: {
                Text("\(Int(current))")
            } minimumValueLabel: {
                Text("\(Int(minimum))")
                    .foregroundColor(.green)
            } maximumValueLabel: {
                Text("\(Int(maximum))")
                    .foregroundColor(.pink)
            }
            .gaugeStyle(.accessoryCircular)
            .tint(Gradient(colors: [.green, .yellow, .orange, .red, .pink]))
            
            Slider(value: $current, in: minimum...maximum) {
                Text("Mimic weather")
            }
            
        }.padding()
    }
}
Replicating Activity Progress Ring
We have learned enough about gauge view that it’s time to put this into a practical example.

We will build Apple Watch’s activity progress rings using gauge view. Here is the code:

struct ContentView: View {
    
    @State private var move = 625.0
    @State private var moveMinimum = 0.0
    @State private var moveMaximum = 900.0
    
    @State private var exercise = 35.0
    @State private var exerciseMinimum = 0.0
    @State private var exerciseMaximum = 45.0
    
    @State private var stand = 6.0
    @State private var standMinimum = 0.0
    @State private var standMaximum = 12.0
    
    var body: some View {
        VStack(spacing: 20) {
            Text("DevTechie")
                .font(.largeTitle)
            
            ZStack {
                Gauge(value: move, in: moveMinimum...moveMaximum) {
                    Text("")
                }
                .scaleEffect(2)
                .gaugeStyle(.accessoryCircularCapacity)
                .tint(.pink)
                
                Gauge(value: exercise, in: exerciseMinimum...exerciseMaximum) {
                    Text("")
                }
                .scaleEffect(1.5)
                .gaugeStyle(.accessoryCircularCapacity)
                .tint(.green)
                
                Gauge(value: stand, in: standMinimum...standMaximum) {
                    Text("")
                }
                .gaugeStyle(.accessoryCircularCapacity)
                .tint(.cyan)
            }
            .frame(width: 200, height: 200)
            
            VStack {
                Slider(value: $move, in: moveMinimum...moveMaximum) {
                    Text("")
                }
                .tint(.pink)
                
                Slider(value: $exercise, in: exerciseMinimum...exerciseMaximum) {
                    Text("")
                }
                .tint(.green)
                
                Slider(value: $stand, in: standMinimum...standMaximum) {
                    Text("")
                }
                .tint(.cyan)
            }
            
        }.padding()
    }
}
Output:



With that we have reached the end of this article. Thank you once again for reading. Subscribe our newsletter at https://www.devtechie.com