Building a Custom MultiSelect Picker in SwiftUI

SwiftUI has revolutionized how we build user interfaces on Apple platforms, making it easier and faster to create modern, responsive apps. While SwiftUI provides excellent built-in controls like Picker
for single selections, there isn’t a native way for multiple selections.
In this tutorial, we will walk through how to build a custom MultiSelectPicker in SwiftUI. the component allows users to select multiple options from a list, we'll also cover how to handle pre-selection of items.
Creating a MultiSelect Picker in SwiftUI
Let’s break down the process step by step.
Step 1: Define the FormOption
Model
We need a model to represent each option in the picker. We’ll use a class FormOption
, it has an isSelected
property to manage the selection state. you can create your own class or struct to suit your project and handle the logic of selection in a way that best fits your needs.
class FormOption: Hashable, Identifiable, Equatable {
var label: String
var value: String?
var uid: String?
var id: String = UUID().uuidString
var isSelected: Bool = false
static func == (lhs: FormOption, rhs: FormOption) -> Bool {
return lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
init(label: String, value: String? = nil, uid: String? = nil, isSelected: Bool = false) {
self.label = label
self.value = value
self.uid = uid
self.isSelected = isSelected
}
}
Why a class? I use a class instead of a struct because a class is a reference type. This ensures that the isSelected
property retains its state across different views. As a result, it becomes easier to filter the selected items from the list of options passed into the picker, which I will demonstrate later in the tutorial
Step 2: Create the Option Item Row
Each option in the picker are displayed in a row. We create a view that will show each option as a Text
and a checkmark if it’s selected.
@available(iOS 13.0, *)
struct MultipleSelectionRow: View {
var option: FormOption
var isSelected: Bool
var onTap: () -> Void
var body: some View {
Button(action: onTap) {
HStack {
Text(option.label)
Spacer()
if isSelected {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
}
}
}
}
}
Step 3: Displaying and Handling Selection
We store all the options in a list and display them using a SwiftUI List
. When the user taps the button to select multiple options, the options are shown in the list, and each can be toggled on or off.
The selection logic is managed by the toggleSelection
function. When a user selects an option, the option's isSelected
property is toggled to true
and toggled back to false
if deselected.
An optional selectedOptions
set is used to keep track of the selected item, primarily used to display the selected items on the UI.
struct OptionsSelectionSheet: View {
var options: [FormOption]
var title: String
@Binding var selectedOptions: Set<FormOption>
@Binding var sheetIsOpen: Bool
var body: some View {
VStack {
HStack {
Text(title)
.foregroundColor(.secondary)
Spacer()
Button(action: {
sheetIsOpen = false
}) {
Text("Done")
}
}
.padding()
List(options, id: \.self) { option in
MultipleSelectionRow(option: option, isSelected: option.isSelected) {
toggleSelection(for: option)
}
}
.listStyle(.plain)
}
}
private func toggleSelection(for option: FormOption) {
option.isSelected.toggle()
if option.isSelected {
selectedOptions.insert(option)
} else {
selectedOptions.remove(option)
}
}
}
FYI: You can modify the logic to manage the selected state using only the selectedOptions
array, which will eliminate the need for the isSelected
property. I will prefer to use both in this tutorial.
I use a set instead of an array because it automatically handles uniqueness and provides faster lookups and removals with O(1) time complexity.
Step 4: Building the Multi Picker View (The Moment We've Been Waiting For)
Next, we create the picker view that will display the list of options, and we’ll modify it to look like a default SwiftUI PickerView. When users tap on the displayed selection, it present the options selection sheet.
public struct MultiOptionListView: View {
public var title: String
@Binding public var options: [FormOption]
@State private var selectedOptions: Set<FormOption> = []
@State private var showOptionsSheet = false
public init(
title: String,
options: Binding<[FormOption]>
) {
self.title = title
self._options = options
}
public var body: some View {
VStack(alignment: .leading) {
Button(action: {
showOptionsSheet.toggle() // Open the sheet
}) {
VStack {
HStack {
// Display selected labels or the title if nothing is selected
Text(selectedLabels.isEmpty ? title : selectedLabels.joined(separator: ", "))
.foregroundColor(selectedLabels.isEmpty ? .gray : .primary)
Spacer()
Image(systemName: "chevron.right.circle")
.foregroundColor(.accentColor)
}
}
.frame(alignment: .leading)
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(10)
}
.sheet(isPresented: $showOptionsSheet) {
OptionsSelectionSheet(
options: $options,
title: title,
selectedOptions: $selectedOptions,
sheetIsOpen: $showOptionsSheet
)
.presentationDetents([.medium])
}
}
.onAppear{
// This ensures that items initially marked as selected
// are automatically pre-selected the first time the user
// opens the picker. Remove this line if your use case does not
// require pre-selection.
selectedOptions = Set(options.filter { $0.isSelected })
}
}
var selectedLabels: [String] {
return options.filter { $0.isSelected }.map { $0.label ?? "" }
}
}
Finally, here's how we can use the picker in our app:
struct ContentView: View {
@State var options: [FormOption] = [
FormOption(label: "Reading", value: "reading"),
FormOption(label: "Traveling", value: "traveling"),
FormOption(label: "Cooking", value: "cooking"),
FormOption(label: "Gaming", value: "gaming")
]
var body: some View {
VStack {
MultiOptionListView(title: "Skills",options: $options)
}
}
}
Wrapping Up
We've successfully built a custom multi-option picker in SwiftUI that allows users to select multiple options with full control of the design and functionality.
I’ve also created a Swift package that you can integrate directly into your project, or simply copy the code and use in your app:
.package(url: "https://github.com/yusuphjoluwasen/MultiSelectPicker.git", from: "1.0.0")
Feel free to tweak the FormOption
model or any part of the code to suit your app's needs.
Happy Coding 💻
Subscribe to my newsletter
Read articles from Jimoh Yusuph directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Jimoh Yusuph
Jimoh Yusuph
I'm an iOS and Full Stack Engineer with a passion for creating secured high-performance applications. I enjoy reading and exploring ways to enhance app functionalities and efficiency. I have strong practical experience developing applications in Swift, Kotlin, Javascript, Python, and Flutter. Having led teams in the past, I greatly value collaboration and teamwork, and I take pleasure in sharing my knowledge with others.