Creating a Segmented Control in SwiftUI
How do you think you would create a segmented control in SwiftUI? Just use UISegmentedControl
, right? Wrong! Similar to the change a while back with UIAlertView rolling into a style under UIAlertController, SwiftUI introduces a View called Picker
that has different styles.
Setting Up
First let’s get set up with what data we’re going to display with our segmented control. It’s super nice outside and I only have a couple of weeks before I start training for triathlon season, so I’m going to pick triathlon sports and the distances for an Ironman triathlon race.
@State private var selectedSport = 0
private let triathlonSports = ["Swim", "Bike", "Run"]
private let ironmanDistances = ["2.4 miles", "112 miles", "26.2 miles"]
I also went ahead and added selectedSport
as our state for keeping track of which segment is currently selected.
Creating a Segmented Control
Now that we have our data source set up we need to create our View. The goal for this example is to end up with a view like the gif above - Some text that shows the selected sport and how far you need to go, and the segmented control populated by the three sports.
VStack(spacing: 100) {
Text("\(triathlonSports[selectedSport]) \(ironmanDistances[selectedSport])")
Picker(selection: $selectedSport, label: Text("Select a Sport")) {
ForEach(0 ..< triathlonSports.count) {
Text(self.triathlonSports[$0])
}
}
}
.padding()
The first two lines create a VStack to hold the text and segmented control and add the text, then the rest sets up the segmented control itself. Let’s look a little closer at how the segmented control is created.
Picker(selection: $selectedSport, label: Text("Select a Sport")) { ... }
In order to create a segmented control you need to instantiate a Picker
. The Picker
’s first argument is a binding to a state property that keeps track of the selected segment, which in our case is the $selectedSport
property. Next is a label to give the user a little extra information.
ForEach(0 ..< triathlonSports.count) {
Text(self.triathlonSports[$0])
}
The rest of the creation of the Picker
tells it what to display on each of the segments. ForEach
is a way to iteratively create views in SwiftUI. What is happening here is basically the same as a for loop that goes from 0 to the number of items in the triathlonSports
array, creating a new Text View for each index and setting the text to the matching sport.
Oh but that doesn’t look right. That just looks like a regular old picker with an ugly label on the left. That’s because we haven’t changed the style to a segmented control! Update your Picker code to include the .pickerStyle
modifier:
Picker(selection: $selectedSport, label: Text("Select a Sport")) {
ForEach(0 ..< triathlonSports.count) {
Text(self.triathlonSports[$0])
}
}
.pickerStyle(SegmentedPickerStyle())
And voilà! The picker updates to a segmented control!
Bonus - How to style a segmented control
Gray is too boring, so let’s spice things up a bit with some color! Because the segmented control we created is actually backed by UISegmentedControl
from UIKit we can take advantage of appearance
to update the look. So if, for example, you wanted a segmented control that has a blue selected color, white text for the selected segment, and blue text for all the other segments you could do so by adding this to your SwiftUI file:
init() {
UISegmentedControl.appearance().selectedSegmentTintColor = .blue
UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.white], for: .selected)
UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.blue], for: .normal)
}