Articles

Generic Segmented Control

Even though with SwiftUI it's finally possible to bind enum types to a segmented control (using the Picker view), I still find myself wishing for more customisability, when it comes to styling this UI component.

If it quacks like an enum...

There isn't such a thing as an Enum protocol, however it's possible to leverage Swift's ability to compose already existing protocols. This allows us to describe properties, required for binding a generic type to some sort of segmented control:


protocol SegmentBindable: CaseIterable, CustomStringConvertible, Hashable { }

Now we can create an enum that conforms to SegmentBindable protocol with only addition being computed description property. In this case enum's raw string value will be passed as the description, but any other logic would work fine too. Let's create an enum representing possible fillings of a pie:


enum Filling: String, SegmentBindable {
    case 🍒, 🥜, 🍓, 🍎
    var description: String { "\(self.rawValue)" }
}

SwiftUI View

After this preparation a SwiftUI View will have all the necessary information to directly get and set values of any enum that conforms to SegmentBindable. Here is a bare-bones example using ForEach:


struct SegmentedControl: View where T: SegmentBindable {
    
    @Binding var value: T
    let segments: Array<T>
    
    init(_ binding: Binding<T>) {
        self._value = binding
        segments = T.allCases as! Array<T>
    }

    var body: some View {
        HStack {
            ForEach(segments, id: \.self) { segment in
                Text(segment.description)
                    .scaleEffect(segment == self.value ? 2 : 1)
                    .animation(.default)
                    .onTapGesture { self.value = segment }
            }
        }
    }
}

Usage

Generic segmented control can be used in just like other SwiftUI control that has a binding. Because of Swift's bi-directional type inference, we don't event need explicitly specify it's type!


struct PieView: View {
    @State private var filling: Filling = .🍎
    
    var body: some View {
        SegmentedControl(value: $filling)
    }
}

Now we can select the filling of our pie by tapping on each emoji like so:

Conclusion

Generic segmented control allows us to create reusable UI for state representing enums in a type safe way. This control can be styled only once and then adapted for any set of options in the app. I have created a Swift Package containing one that's styled to look like UISegmentedControl.