Picker in SwiftUI

DevTechie Inc
Jan 6, 2023

Picker is a control which is used for selecting from a set of mutually exclusive values. It combines UISegmentedControland UIPickerView in one. Picker adapts the style of the platform it’s being rendered.

Picker is created by providing a selection binding, label and content for the picker label. Selection parameter is a bound property which provides the value to display for current selection.

Let’s look at an example.

We will start with an enum to store picker values we want to show. This enum will be String type and we will make it CaseIterable so we can iterate over the values. We also want to conform to the identifiable for this enum so each value can be identified uniquely.

We will have two computed properties for this enum, one to provide identity and other to return string representation of the enum value.

enum DevTechieCourseCategories: String, CaseIterable, Identifiable {
    case swiftUI, uiKit, ml, swift
    
    var id: Self { self }
    
    var name: String {
        switch self {
        case .swift: return "Swift"
        case .ml: return "Machine Learning"
        case .swiftUI: return "SwiftUI"
        case .uiKit: return "UIKit"
        }
    }
}
Next, we will create a view to host Picker view. We also want to create a State variable so we can bind selectionparameter for Picker view.

struct DevTechiePickerExample: View {    @State private var selectedCategory: DevTechieCourseCategories = .swiftUI    var body: some View {
        NavigationStack {
            Picker("Course Categories", selection: $selectedCategory) {
               
            }
            .navigationTitle("DevTechie.com")
        }
    }
}
We can create picker to select among the values by providing a collection of views for the picker content. We will start with simple Text view.

Text(DevTechieCourseCategories.swiftUI.name)
    .tag(DevTechieCourseCategories.swiftUI)Text(DevTechieCourseCategories.uiKit.name)
    .tag(DevTechieCourseCategories.uiKit)Text(DevTechieCourseCategories.ml.name)
    .tag(DevTechieCourseCategories.ml)Text(DevTechieCourseCategories.swift.name)
    .tag(DevTechieCourseCategories.swift)
Our complete view will look like this:

struct DevTechiePickerExample: View {
    @State private var selectedCategory: DevTechieCourseCategories = .swiftUI
    var body: some View {
        NavigationStack {
            Picker("Course Categories", selection: $selectedCategory) {
                
                Text(DevTechieCourseCategories.swiftUI.name)
                    .tag(DevTechieCourseCategories.swiftUI)
                
                Text(DevTechieCourseCategories.uiKit.name)
                    .tag(DevTechieCourseCategories.uiKit)
                
                Text(DevTechieCourseCategories.ml.name)
                    .tag(DevTechieCourseCategories.ml)
                
                Text(DevTechieCourseCategories.swift.name)
                    .tag(DevTechieCourseCategories.swift)
                
            }
            .navigationTitle("DevTechie.com")
        }
    }
}
Build and run:

Note that the selection property actually looks at the tag property in Picker content to observe and reflect the change.
Because our enum conforms to CaseIterable, Identifiable protocols, we can optimize our code by wrapping content into a ForEach loop.

struct DevTechiePickerExample: View {
    @State private var selectedCategory: DevTechieCourseCategories = .swiftUI
    var body: some View {
        NavigationStack {
            Picker("Course Categories", selection: $selectedCategory) {
                
                ForEach(DevTechieCourseCategories.allCases) { category in
                    Text(category.name)
                        .tag(category)
                }
            }
            .navigationTitle("DevTechie.com")
        }
    }
}
Our app should work same as before.

Note: By default on iOS(starting iOS 15) default picker style is menu style. Also starting iOS 16, we get up down chevron for the label.
Starting iOS 16, Picker in List appears as menu style as well.

struct DevTechiePickerExample: View {
    @State private var selectedCategory: DevTechieCourseCategories = .swiftUI
    var body: some View {
        NavigationStack {
            List {
                Picker("Course Categories", selection: $selectedCategory) {
                    
                    ForEach(DevTechieCourseCategories.allCases) { category in
                        Text(category.name)
                            .tag(category)
                    }
                }
            }
            .navigationTitle("DevTechie.com")
        }
    }
}
tag function doesn’t have to be bound to the type being selected, its there to keep the current selection but if you want to create a view where based upon Picker selection, you want to show some other value, we can do that. Let’s build an example.

Let’s update our code to suggest DevTechie’s video course based on selected category.

We will start by creating another enum to show a few DevTechie video courses(only a few there are more at DevTechie.com 😃).

enum DevTechieCourse: String, CaseIterable, Identifiable {
    case materingSwiftUI4, machineLearningIniOS, learnSwiftByExample, buildStravaCloneInUiKit
    
    var id: Self { self }
    
    var name: String {
        switch self {
        case .materingSwiftUI4: return "Mastering SwiftUI 4"
        case .machineLearningIniOS: return "Machine Learning in iOS"
        case .buildStravaCloneInUiKit: return "Build Strava Clone in UIKit"
        case .learnSwiftByExample: return "Learn Swift By Example"
        }
    }
}
We will add an extension to course category enum and create another computed property to return suggested video course based on selected category.

extension DevTechieCourseCategories {
    var suggestedCourse: DevTechieCourse {
        switch self {
        case .swiftUI: return .materingSwiftUI4
        case .ml: return .machineLearningIniOS
        case .swift: return .learnSwiftByExample
        case .uiKit: return .buildStravaCloneInUiKit
        }
    }
}
Next, we will replace State variable with a new one to keep suggestedCourse:

@State private var suggestedCourse = DevTechieCourse.materingSwiftUI4
We will change our picker to bind selected to this newly created State vaiable.

Picker("Course Categories", selection: $suggestedCourse)
We will change tag to use newly created variable from extension

Text(category.name).tag(category.suggestedCourse)
We also need to add section to show suggested course

Section {
    Text(suggestedCourse.name)
        .foregroundColor(.secondary)
} header: {
    Text("Suggested Course")
}
Our complete code should look like this:

enum DevTechieCourseCategories: String, CaseIterable, Identifiable {
    case swiftUI, uiKit, ml, swift
    
    var id: Self { self }
    
    var name: String {
        switch self {
        case .swift: return "Swift"
        case .ml: return "Machine Learning"
        case .swiftUI: return "SwiftUI"
        case .uiKit: return "UIKit"
        }
    }
}enum DevTechieCourse: String, CaseIterable, Identifiable {
    case materingSwiftUI4, machineLearningIniOS, learnSwiftByExample, buildStravaCloneInUiKit
    
    var id: Self { self }
    
    var name: String {
        switch self {
        case .materingSwiftUI4: return "Mastering SwiftUI 4"
        case .machineLearningIniOS: return "Machine Learning in iOS"
        case .buildStravaCloneInUiKit: return "Build Strava Clone in UIKit"
        case .learnSwiftByExample: return "Learn Swift By Example"
        }
    }
}extension DevTechieCourseCategories {
    var suggestedCourse: DevTechieCourse {
        switch self {
        case .swiftUI: return .materingSwiftUI4
        case .ml: return .machineLearningIniOS
        case .swift: return .learnSwiftByExample
        case .uiKit: return .buildStravaCloneInUiKit
        }
    }
}struct DevTechiePickerExample: View {
       
    @State private var suggestedCourse = DevTechieCourse.materingSwiftUI4
    
    var body: some View {
        NavigationStack {
            List {
                Picker("Course Categories", selection: $suggestedCourse) {
                    
                    ForEach(DevTechieCourseCategories.allCases) { category in
                        Text(category.name)
                            .tag(category.suggestedCourse)
                    }
                    
                }
                
                Section {
                    Text(suggestedCourse.name)
                        .foregroundColor(.secondary)
                } header: {
                    Text("Suggested Course")
                }}
            .navigationTitle("DevTechie.com")
        }
    }
}
Build and run:

This example shows that Picker is bound to DevTechieCourse type while the options are all DevTechieCourseCategory instances. Each option uses the tag modifier to associate the suggested course with the category it displays.

Styling Pickers
We can change Picker style with pickerStyle modifier.

struct DevTechiePickerExample: View {
       
    @State private var suggestedCourse = DevTechieCourse.materingSwiftUI4
    
    var body: some View {
        NavigationStack {
            List {
                Picker("Course Categories", selection: $suggestedCourse) {
                    
                    ForEach(DevTechieCourseCategories.allCases) { category in
                        Text(category.name)
                            .tag(category.suggestedCourse)
                    }
                    
                }
                .pickerStyle(.segmented)
                
                Section {
                    Text(suggestedCourse.name)
                        .foregroundColor(.secondary)
                } header: {
                    Text("Suggested Course")
                }}
            .navigationTitle("DevTechie.com")
        }
    }
}
.pickerStyle(.wheel)
.pickerStyle(.inline)
.pickerStyle(.inline)
We can create sections in Picker view. Let’s start by adding new course categories.

enum DevTechieCourseCategories: String, CaseIterable, Identifiable {
    case swiftUI, uiKit, ml, swift
    case flutter, kotlin
    
    var id: Self { self }
    
    var name: String {
        switch self {
        case .swift: return "Swift"
        case .ml: return "Machine Learning"
        case .swiftUI: return "SwiftUI"
        case .uiKit: return "UIKit"
        case .flutter: return "Flutter"
        case .kotlin: return "Kotlin"
        }
    }
}
To match, we will update DevTechieCourse enum along with category extension:

enum DevTechieCourse: String, CaseIterable, Identifiable {
    case materingSwiftUI4, machineLearningIniOS, learnSwiftByExample, buildStravaCloneInUiKit
    
    case flutterDevForiOS, androidWithKotlin
    
    var id: Self { self }
    
    var name: String {
        switch self {
        case .materingSwiftUI4: return "Mastering SwiftUI 4"
        case .machineLearningIniOS: return "Machine Learning in iOS"
        case .buildStravaCloneInUiKit: return "Build Strava Clone in UIKit"
        case .learnSwiftByExample: return "Learn Swift By Example"case .flutterDevForiOS: return "Flutter Dev for iOS"
        case .androidWithKotlin: return "Learn Android with Kotlin"
        }
    }
}extension DevTechieCourseCategories {
    var suggestedCourse: DevTechieCourse {
        switch self {
        case .swiftUI: return .materingSwiftUI4
        case .ml: return .machineLearningIniOS
        case .swift: return .learnSwiftByExample
        case .uiKit: return .buildStravaCloneInUiKit
        case .flutter: return .flutterDevForiOS
        case .kotlin: return .androidWithKotlin
        }
    }
}
Let’s refactor our view to create two category collections so we can add sections to the Picker view.

var category1 = [DevTechieCourseCategories.swiftUI, .swift, .ml, .uiKit]
var category2 = [DevTechieCourseCategories.flutter, .kotlin]
We will update Picker body as well:

Section {
    ForEach(category1) { category in
        Text(category.name)
            .tag(category.suggestedCourse)
    }
} header: {
    Text("iOS & Related")
}Section {
    ForEach(category2) { category in
        Text(category.name)
            .tag(category.suggestedCourse)
    }
} header: {
    Text("Others")
}
Complete code with all the latest changes and refactoring:

enum DevTechieCourseCategories: String, CaseIterable, Identifiable {
    case swiftUI, uiKit, ml, swift
    case flutter, kotlin
    
    var id: Self { self }
    
    var name: String {
        switch self {
        case .swift: return "Swift"
        case .ml: return "Machine Learning"
        case .swiftUI: return "SwiftUI"
        case .uiKit: return "UIKit"
        case .flutter: return "Flutter"
        case .kotlin: return "Kotlin"
        }
    }
}enum DevTechieCourse: String, CaseIterable, Identifiable {
    case materingSwiftUI4, machineLearningIniOS, learnSwiftByExample, buildStravaCloneInUiKit
    
    case flutterDevForiOS, androidWithKotlin
    
    var id: Self { self }
    
    var name: String {
        switch self {
        case .materingSwiftUI4: return "Mastering SwiftUI 4"
        case .machineLearningIniOS: return "Machine Learning in iOS"
        case .buildStravaCloneInUiKit: return "Build Strava Clone in UIKit"
        case .learnSwiftByExample: return "Learn Swift By Example"case .flutterDevForiOS: return "Flutter Dev for iOS"
        case .androidWithKotlin: return "Learn Android with Kotlin"
        }
    }
}extension DevTechieCourseCategories {
    var suggestedCourse: DevTechieCourse {
        switch self {
        case .swiftUI: return .materingSwiftUI4
        case .ml: return .machineLearningIniOS
        case .swift: return .learnSwiftByExample
        case .uiKit: return .buildStravaCloneInUiKit
        case .flutter: return .flutterDevForiOS
        case .kotlin: return .androidWithKotlin
        }
    }
}struct DevTechiePickerExample: View {
       
    @State private var suggestedCourse = DevTechieCourse.materingSwiftUI4
    
    var category1 = [DevTechieCourseCategories.swiftUI, .swift, .ml, .uiKit]
    
    var category2 = [DevTechieCourseCategories.flutter, .kotlin]
    
    var body: some View {
        NavigationStack {
            List {
                Picker("Course Categories", selection: $suggestedCourse) {
                    
                    Section {
                        ForEach(category1) { category in
                            Text(category.name)
                                .tag(category.suggestedCourse)
                        }
                    } header: {
                        Text("iOS & Related")
                    }Section {
                        ForEach(category2) { category in
                            Text(category.name)
                                .tag(category.suggestedCourse)
                        }
                    } header: {
                        Text("Others")
                    }
                }
                
                Section {
                    Text(suggestedCourse.name)
                        .foregroundColor(.secondary)
                } header: {
                    Text("Suggested Course")
                }}
            .navigationTitle("DevTechie.com")
        }
    }
}
Picker label customization is limited but we can add images to the text as shown below:

Section {
    ForEach(category1) { category in
        HStack {
            Text(category.name)
            Spacer()
            Image(systemName: "graduationcap.fill")
        }
        .tag(category.suggestedCourse)
    }
} header: {
    Text("iOS & Related")
}
Note: Picker selection seems to keep image around even for the entries where there is no image (Flutterand Kotlin are the example here). It could be a bug or feature 😉


With that we have reached the end of this article. Thank you once again for reading. Don’t forget to follow 😍. Also subscribe our newsletter at https://www.devtechie.com