Segmented Control in SwiftUI 

DevTechie Inc
Jun 11, 2022

Photo by Timo Volz on Unsplash

Segmented control from UIKit is rolled into Picker view in SwiftUI. With the help of pickerStyle modifier, we can get our good old segmented control back.

Picker is created by providing a selection binding, label, and content for the picker to display and pickerStyle of type .segmented turns default picker into a segmented control.

Let’s create an example to understand this a little better.

We will start with a State variable which will hold the current selection for the picker. We will use tag function to identify different values of picker view’s items. This tag value can be any hashable type and upon selection will modify our State variable which eventually will trigger UI update for the view. Next we will use pickerStyle to change our picker into a segmented control.

struct PickerExample: View {
    @State private var selected = 0
    var body: some View {
        Picker("Choose course", selection: $selected) {
            Text("SwiftUI")
                .tag(0)
            Text("UIKit")
                .tag(1)
        }
        .pickerStyle(.segmented)
    }
}
Using Strings for Selection
In previous example, we saw use of selection State variable to track currently selected value in segmented control. In that case selection variable stored integer type value but we can change that to use string type as well.

We will update our previous example as shown below:

struct PickerExample: View {
    @State private var selected = "SwiftUI"
    var body: some View {
        Picker("Choose course", selection: $selected) {
            Text("SwiftUI")
                .tag("SwiftUI")
            Text("UIKit")
                .tag("UIKit")
        }
        .pickerStyle(.segmented)
    }
}
Using enum for Selection
We can use enum, bind that to selection and use it with picker segment view as shown below:

enum Category: String {
    case swiftUI = "SwiftUI"
    case uIKit = "UIKit"
}struct PickerExample: View {
    @State private var selected = Category.swiftUI
    var body: some View {
        Picker("Choose course", selection: $selected) {
            Text(Category.swiftUI.rawValue)
                .tag(Category.swiftUI)
            Text(Category.uIKit.rawValue)
                .tag(Category.uIKit)
        }
        .pickerStyle(.segmented)
    }
}
ForEach and Segment Control
So far we have been using individual text views to display as segment for our view but if we have collection to iterate over then we can do that too, with the help of ForEach.

struct PickerExample: View {
    @State private var selected = "SwiftUI"
    let segments = ["SwiftUI", "UIKit", "ML", "CV"]
    var body: some View {
        Picker("Choose course", selection: $selected) {
            ForEach(segments, id:\.self) { segment in 
                Text(segment)
                    .tag(segment)
            }
        }
        .pickerStyle(.segmented)
    }
}
Customization to SegmentControl
There are only few customization options available for the segment control that comes out of box. We can set backgroundColor, cornerRadius, padding etc for the view but not the foregroundColor or foregroundStyle doesn’t work yet.

struct PickerExample: View {
    @State private var selected = "SwiftUI"
    let segments = ["SwiftUI", "UIKit", "ML", "CV"]
    var body: some View {
        Picker("Choose course", selection: $selected) {
            ForEach(segments, id:\.self) { segment in 
                Text(segment)
                    .tag(segment)
            }
        }
        .pickerStyle(.segmented)
        .background(Color.orange)
        .cornerRadius(50)
        .padding()    }
}
Custom Segmented Control
Even though we don’t have much customization available out of the box but we can easily build a custom segmented control by combining some of the existing SwiftUI views as shown in the code below:

struct CustomSegmentedPickerView: View {
    @State private var selection = 0
    private var titles = ["SwiftUI", "UIKit", "ML", "CV"]
    private var colors = [Color.purple, Color.blue, Color.pink, Color.green]
    @State private var frames = Array<CGRect>(repeating: .zero, count: 4)
    
    var body: some View {
        VStack {
            ZStack {
                HStack(spacing: 10) {
                    ForEach(self.titles.indices, id: \.self) { index in
                        Button(action: { self.selection = index }) {
                            Text(self.titles[index])
                                .foregroundColor(.white)
                        }.padding(EdgeInsets(top: 16, leading: 20, bottom: 16, trailing: 20)).background(
                            GeometryReader { geo in
                                Color.clear.onAppear { self.setFrame(index: index, frame: geo.frame(in: .global)) }
                            }
                        )
                    }
                }
                .background(
                    Capsule().fill(
                        self.colors[self.selection].opacity(0.4))
                        .frame(width: self.frames[self.selection].width,
                               height: self.frames[self.selection].height, alignment: .topLeading)
                        .offset(x: self.frames[self.selection].minX - self.frames[0].minX)
                    , alignment: .leading
                )
            }
            .animation(.default, value: selection)
            .background(Capsule().stroke(Color.purple, lineWidth: 3))
            
            Text("Value: \(self.titles[self.selection])")
                .font(.largeTitle)
            
            Picker(selection: self.$selection, label: Text("What is your favorite color?")) {
                ForEach(0..<self.titles.count) { index in
                    Text(self.titles[index]).tag(index)
                }
            }.pickerStyle(SegmentedPickerStyle())
        }
    }
    
    func setFrame(index: Int, frame: CGRect) {
        self.frames[index] = frame
    }
}
With that we have reached the end of this article. Thank you once again for reading. Don’t forget to subscribe to our weekly newsletter at https://www.devtechie.com