How to use DatePicker to select dates in SwiftUI | SwiftUI Bootcamp #40
Selecting dates is a fundamental requirement in apps ranging from booking systems to reminders and birthday fields. By the end of this lesson you'll know how to present a date picker with a constrained date range, format the selected value for display, and switch between visual styles — all with just a few lines of SwiftUI.
What You'll Learn
- How to bind a
DatePickerto a@Statevariable so the UI updates automatically when the user picks a date - How to restrict the selectable range using a
ClosedRangeofDatevalues built fromCalendarandDateComponents - How to switch between
CompactDatePickerStyle,GraphicalDatePickerStyle, andWheelDatePickerStyle - How to format a
Datefor human-readable display usingDateFormatter
Mental Model
Think of DatePicker like a dial on a safe: the dial itself is the picker, the combination you land on is the Date value, and your @State variable is the lock mechanism that remembers which combination is currently set. When the user spins the dial, the combination updates instantly, and anything in the UI that reads that combination — like the formatted text label — automatically reflects the new value.
The date range constraint works like a physical stop on that dial: you physically cannot spin past the minimum or maximum position. In code you express that stop with in: startingDate...endingDate, and SwiftUI grays out the dates outside that window so the user always knows what is off-limits.
Detailed Explanation
DatePicker is a SwiftUI control that presents a native calendar or wheel interface and writes the user's selection back to a binding. Because it's bound to @State var selectedDate: Date, every change the user makes triggers a view re-render — the formatted text label always shows the latest pick without any extra event handling.
Under the hood, DatePicker reads your range and displayedComponents parameters once at init time and passes them to the underlying UIDatePicker (on iOS) or NSDatePicker (on macOS). SwiftUI takes care of the two-way synchronisation, so you never manually observe or set the date yourself.
Use DatePicker when the user must specify a precise date or time, especially when legal or business constraints apply (e.g., you must be at least 18, or a booking can only be made up to 30 days in advance). Avoid it when a relative selection ("in 3 days", "next Monday") would be clearer — in those cases a Stepper or Picker with pre-computed options creates less cognitive friction.
DateFormatter is kept in a computed property rather than a stored constant so it is only created when the view body reads it. In production code you would typically keep a single shared formatter at a higher scope to avoid re-creation costs.
Code Structure
DatePickerBootcamp.swift contains a single view struct that demonstrates the complete lifecycle: defining the date range with Calendar, binding the picker to @State, formatting the output with DateFormatter, and toggling between picker styles. The commented-out lines show progressively simpler configurations you can experiment with.
Complete Code
DatePickerBootcamp.swift
import SwiftUI
struct DatePickerBootcamp: View {
@State var selectedDate: Date = Date() // tracks whichever date the user has chosen; starts at "now"
let startingDate: Date = Calendar.current.date(from: DateComponents(year: 2018)) ?? Date() // lower bound — nil-coalescences to today if Calendar fails
let endingDate: Date = Date() // upper bound is the current moment, preventing future selections
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .short // produces output like "6/27/26"
formatter.timeStyle = .short // appends a time like "10:30 AM"
return formatter
}
var body: some View {
VStack {
Text("SELECTED DATE IS:")
Text(dateFormatter.string(from: selectedDate))
.font(.title)
//DatePicker("Select a Date", selection: $selectedDate)
//DatePicker("Select a date", selection: $selectedDate, displayedComponents: [.date, .hourAndMinute])
DatePicker("Select a date", selection: $selectedDate, in: startingDate...endingDate, displayedComponents: [.date])
.tint(Color.red) // colours the interactive elements (selected ring, etc.) in the picker
.datePickerStyle(
CompactDatePickerStyle() // shows a compact inline row that expands to a calendar when tapped
//GraphicalDatePickerStyle() // always shows a full month calendar grid
//WheelDatePickerStyle() // classic scrolling drum roll, good for time-only pickers
)
}
}
}
struct DatePickerBootcamp_Previews: PreviewProvider {
static var previews: some View {
DatePickerBootcamp()
}
}Code Walkthrough
@State var selectedDate— Declares the single source of truth for the chosen date. Because it is@State, SwiftUI automatically re-renders the view whenever this value changes. Starting it atDate()means the picker opens showing today.startingDateandendingDate— These twoletconstants define the valid window.Calendar.current.date(from: DateComponents(year: 2018))constructs January 1 2018 at midnight in the user's current time zone — a clean way to build an exact date without hard-coding a time string.dateFormattercomputed property — ADateFormatterconfigured for short date and short time output. Keeping it as a computed property on the view means it is recreated on each render cycle; for performance-critical screens move the formatter to a static or shared location.Text(dateFormatter.string(from: selectedDate))— ReadsselectedDateat render time and converts it to a human-readable string. BecauseselectedDateis@State, this text automatically stays in sync with what the picker shows.DatePicker("Select a date", selection: $selectedDate, in: startingDate...endingDate, displayedComponents: [.date])— The four-argument form. The label is used for accessibility (VoiceOver reads it),$selectedDateis the two-way binding,in:restricts the range, anddisplayedComponents: [.date]hides the time segment entirely..tint(Color.red)— Applies the tint color to the picker's interactive accents. Prior to iOS 15 you would use.accentColor(.red)for the same effect;tintis the modern replacement.Commented-out picker style variants —
GraphicalDatePickerStyle()renders a full calendar grid, which is great for booking UIs.WheelDatePickerStyle()uses the classic drum-roll style, which works well for time-only or compact forms. Swap styles freely to suit your design.
Common Mistakes
Mistake: Using Date() as both the starting and ending date, making the range zero-width
This happens when developers copy the ending-date pattern and accidentally assign the same expression to both bounds. The picker shows nothing selectable or crashes. Fix it by ensuring startingDate is definitively earlier than endingDate. Use Calendar.current.date(byAdding:) to offset if needed.
Mistake: Formatting dates inside the view body with a new DateFormatter every renderDateFormatter is expensive to create. If you put let formatter = DateFormatter() directly inside body, it re-runs on every render — which can happen dozens of times per second during animations. Lift it to a computed property on the struct (as shown here) or to a static property for truly hot paths.
Mistake: Ignoring displayedComponents and then wondering why the picker shows a time wheel
By default DatePicker shows both date and time. If your feature only cares about the date (e.g., a birthday field), pass displayedComponents: [.date] explicitly. Leaving it as the default makes the UI confusing and also makes the selected value harder to compare since the time component varies.
Key Takeaways
- Bind
DatePickerto a@State var selectedDate: Date— SwiftUI handles all re-rendering automatically when the user changes the value. - Use
in: startingDate...endingDateto enforce valid date ranges at the UI level, preventing impossible or out-of-policy selections. - Choose your style (
CompactDatePickerStyle,GraphicalDatePickerStyle, orWheelDatePickerStyle) based on available screen space and how central the date selection is to the screen's purpose.
Last updated: June 27, 2026