For years, was difficult for any iOS Developer the need to allow the user to select more than one date on a calendar. UIKit did not offer a direct native solution, forcing developers to rely on third-party libraries (like FSCalendar) or build complex custom CollectionView layouts with calendar logic from scratch.
With the arrival of iOS 16 and recent Xcode updates, Apple introduced the component we were all waiting for in SwiftUI: MultiDatePicker.
In this Swift programming tutorial, we will explore in depth how to select multiple dates in SwiftUI, manage the complex DateComponents data structure, and adapt our implementation for iOS, macOS, and watchOS.
What is MultiDatePicker and why is it revolutionary?
MultiDatePicker is a control view that allows the selection of zero, one, or multiple dates. Unlike the traditional DatePicker (which manages a single point in time), this new control is designed for use cases such as:
- Selecting vacation days.
- Managing rotating work shifts.
- Habit Trackers.
- Planning irregular recurring events.
The Paradigm Shift: Date vs DateComponents
For the iOS Developer accustomed to working with Date objects, MultiDatePicker presents a significant learning curve. It does not work with Date. It works with Set<DateComponents>.
Why? Because Date represents a precise instant in universal time (seconds since 1970). However, “December 25th” is a calendar concept. Apple uses DateComponents to ensure the selected date is robust against time zones and calendar systems (Gregorian, Buddhist, etc.).
Basic Implementation on iOS
Let’s open Xcode and start with the basics. We need a state variable to store a set (Set) of date components.
Step 1: The State and the View
import SwiftUI
struct BasicMultiDateView: View {
// The source of truth. We use a Set to automatically avoid duplicates.
@State private var selectedDates: Set<DateComponents> = []
var body: some View {
VStack {
Text("Selected days: \(selectedDates.count)")
.font(.headline)
.padding()
// The star component
MultiDatePicker("Select dates", selection: $selectedDates)
.padding()
}
}
}
When running this in the iOS simulator, you will see a native calendar where you can tap multiple days. Selected days are marked with a filled circle.
Working with Data: From Components to Real Dates
Here is where most Swift programming tutorials fall short. Having a Set<DateComponents> is useful for UI, but your backend or database (CoreData/SwiftData) probably expects Date objects.
Step 2: Conversion and Sorting
Let’s create a computed property that transforms our selection into something readable.
var formattedDates: [String] {
let calendar = Calendar.current
// 1. Convert Components to Date
let dates = selectedDates.compactMap { components -> Date? in
// It is vital to force the time to noon or start of day to avoid time zone errors
calendar.date(from: components)
}
// 2. Sort chronologically
let sortedDates = dates.sorted()
// 3. Format to String
return sortedDates.map { date in
date.formatted(date: .long, time: .omitted)
}
}
Now we can show the list below the calendar:
List(formattedDates, id: \.self) { dateString in
Text(dateString)
}
Restrictions and Bounds
A common scenario when selecting multiple dates in SwiftUI is limiting the range. For example, in a payroll app, you might only want to allow selecting days in the current month.
MultiDatePicker accepts the in: (range) parameter, similar to the standard DatePicker.
struct PayrollView: View {
@State private var selectedDates: Set<DateComponents> = []
// Define the range: From today to end of year
var bounds: Range<Date> {
let now = Date()
let calendar = Calendar.current
let endOfYear = calendar.date(from: DateComponents(year: 2024, month: 12, day: 31))!
return now..<endOfYear
}
var body: some View {
MultiDatePicker("Payroll", selection: $selectedDates, in: bounds)
}
}
By using in: bounds, the calendar will visually disable (gray out) all days outside that range, preventing user interaction.
Multiplatform Adaptation in Xcode
SwiftUI code is portable, but the user experience must adapt.
MultiDatePicker on macOS
On macOS, the paradigm changes. We have more space and a mouse pointer. MultiDatePicker on macOS automatically renders as a desktop calendar.
Pro Tip for macOS: Unlike iOS, where the calendar usually occupies the screen width, on macOS you’ll want to restrict its size or put it in a Popover or sidebar.
// Optimized code for macOS
MultiDatePicker("Dates", selection: $selectedDates)
.frame(width: 300) // Important to limit width on desktop
.background(Material.ultraThin)
.cornerRadius(10)
MultiDatePicker on watchOS 10+
The Apple Watch received support for this control recently. Given the screen size, the design changes radically. Apple presents a compact view that navigates to a full-month detail view when tapped.
Advanced Tutorial: “Shift Manager” App
Let’s combine everything we’ve learned into a real Swift programming example. We will create a view for an employee to select their remote work days.
The ViewModel
import SwiftUI
@Observable // Swift 5.9+ Macro
class ShiftManager {
var selectedDays: Set<DateComponents> = []
// Validation logic: Do not allow weekends
func validateSelection() {
let calendar = Calendar.current
selectedDays = selectedDays.filter { components in
guard let date = calendar.date(from: components) else { return false }
let weekday = calendar.component(.weekday, from: date)
// 1 = Sunday, 7 = Saturday. We keep only Monday to Friday.
return weekday != 1 && weekday != 7
}
}
// Reset
func clearAll() {
selectedDays.removeAll()
}
}
The Main View
struct ShiftSelectorView: View {
@State private var manager = ShiftManager()
@Environment(\.calendar) var calendar // Use environment calendar
var body: some View {
NavigationStack {
VStack(spacing: 0) {
// Info Header
HStack {
VStack(alignment: .leading) {
Text("Remote Work")
.font(.title2.bold())
Text("\(manager.selectedDays.count) days selected")
.foregroundStyle(.secondary)
}
Spacer()
Button("Clear") {
withAnimation { manager.clearAll() }
}
.buttonStyle(.bordered)
.tint(.red)
}
.padding()
Divider()
// The Selector
MultiDatePicker("Select days", selection: $manager.selectedDays)
.tint(.indigo) // Customize selection color
.padding()
.onChange(of: manager.selectedDays) {
// Reactive validation
manager.validateSelection()
}
// Confirmation List
List {
Section("Summary") {
if manager.selectedDays.isEmpty {
ContentUnavailableView("No dates", systemImage: "calendar.badge.minus")
} else {
ForEach(manager.selectedDays.sorted(by: {
// Sorting DateComponents is tricky, better convert to Date if critical
($0.year ?? 0, $0.month ?? 0, $0.day ?? 0) < ($1.year ?? 0, $1.month ?? 0, $1.day ?? 0)
}), id: \.self) { day in
Label {
Text("\(day.day!)/\(day.month!)/\(day.year!)")
} icon: {
Image(systemName: "house.fill")
.foregroundStyle(.indigo)
}
}
}
}
}
}
.navigationTitle("Planning")
}
}
}
Expert Tips and Tricks (Xcode Tips)
To stand out as an iOS Developer, you must know the finer details:
1. The Calendar Environment (.environment)
MultiDatePicker uses Calendar.current by default. If your app needs to force a specific calendar (e.g., Gregorian on a device set to Buddhist), you must inject it.
MultiDatePicker(...)
.environment(\.calendar, Calendar(identifier: .gregorian))
2. Performance
If you select hundreds of dates (e.g., a whole year), performance can degrade if you try to convert and sort the array on the main thread inside the body. Move sorting logic to a background Task or use Swift Concurrency if handling massive volumes of dates.
3. Limited Customization
Unlike third-party libraries, you cannot easily change the background color of a specific day (e.g., painting holidays red and selected days blue) within the standard MultiDatePicker. If you need that, MultiDatePicker is not the tool; you will need UICalendarView (via UIViewRepresentable) which offers delegates for custom decorations.
Conclusion
The ability to select multiple dates in SwiftUI with MultiDatePicker has democratized the creation of complex calendar interfaces. What used to take weeks of custom development or heavy dependencies can now be achieved with a few lines of native code in Xcode.
Although working with Set<DateComponents> requires a mental shift from using Date, the robustness it offers in terms of calendar precision is worth it. As an iOS Developer, integrating this component into your iOS, macOS, and watchOS apps not only modernizes your interface but ensures you are using the best accessibility and design practices of the Apple ecosystem.
If you have any questions about this article, please contact me and I will be happy to help you 🙂. You can contact me on my X profile or on my Instagram profile.