How to implement a date picker in SwiftUI
Bind a Date to DatePicker and pick a style — compact, graphical, or wheel. That's all you need for a fully functional, accessible date picker in SwiftUI.
import SwiftUI
struct QuickDatePickerView: View {
@State private var selectedDate = Date()
var body: some View {
Form {
DatePicker(
"Select a date",
selection: $selectedDate,
displayedComponents: [.date]
)
.datePickerStyle(.compact)
}
}
}
#Preview {
QuickDatePickerView()
}
Full implementation
The example below shows a realistic form scenario — booking a future appointment — with a constrained date range, a graphical calendar picker, and a separate time picker. A formatted summary string below the controls lets the user confirm their selection at a glance.
import SwiftUI
struct AppointmentFormView: View {
@State private var appointmentDate = Date()
@State private var appointmentTime = Date()
@State private var showGraphical = false
// Only allow bookings from today up to one year ahead
private var dateRange: ClosedRange<Date> {
let calendar = Calendar.current
let start = calendar.startOfDay(for: Date())
let end = calendar.date(
byAdding: .year, value: 1, to: start
) ?? start
return start...end
}
private var formattedSummary: String {
let dateFmt = Date.FormatStyle().month(.wide).day().year()
let timeFmt = Date.FormatStyle().hour().minute()
return "\(appointmentDate.formatted(dateFmt)) at \(appointmentTime.formatted(timeFmt))"
}
var body: some View {
NavigationStack {
Form {
// MARK: - Date section
Section("Appointment date") {
Toggle("Show calendar", isOn: $showGraphical)
if showGraphical {
DatePicker(
"Date",
selection: $appointmentDate,
in: dateRange,
displayedComponents: [.date]
)
.datePickerStyle(.graphical)
.labelsHidden()
.accessibilityLabel("Appointment date calendar")
} else {
DatePicker(
"Date",
selection: $appointmentDate,
in: dateRange,
displayedComponents: [.date]
)
.datePickerStyle(.compact)
}
}
// MARK: - Time section
Section("Appointment time") {
DatePicker(
"Time",
selection: $appointmentTime,
displayedComponents: [.hourAndMinute]
)
.datePickerStyle(.wheel)
.labelsHidden()
.frame(maxWidth: .infinity)
.accessibilityLabel("Appointment time")
}
// MARK: - Summary
Section("Summary") {
Text(formattedSummary)
.font(.subheadline)
.foregroundStyle(.secondary)
.accessibilityLabel("Selected: \(formattedSummary)")
}
// MARK: - Confirm
Section {
Button("Confirm appointment") {
// Handle booking
}
.frame(maxWidth: .infinity)
.buttonStyle(.borderedProminent)
}
.listRowBackground(Color.clear)
}
.navigationTitle("Book Appointment")
.navigationBarTitleDisplayMode(.inline)
}
}
}
#Preview {
AppointmentFormView()
}
How it works
-
@State private var appointmentDate = Date()— A@Stateproperty stores the current selection. SwiftUI re-renders the view automatically whenever its value changes via the two-way$appointmentDatebinding. -
in: dateRange— TheClosedRange<Date>computed property prevents users from picking dates in the past or more than a year ahead. SwiftUI greys out out-of-range dates automatically in graphical and wheel styles. -
displayedComponents: [.date]vs[.hourAndMinute]— Splitting date and time into two separateDatePickercontrols (each with a singleDisplayedComponentsoption) gives you independent layout control and cleaner VoiceOver announcements. -
.datePickerStyle(.graphical)— The toggle switches between.compact(a tappable label that expands inline) and.graphical(a full month calendar grid). Both are native UIKit-backed controls with zero extra dependencies. -
Date.FormatStyle()— iOS 15+ format styles replace the oldDateFormatter. TheformattedSummarycomputed property produces a localised, human-readable string using the device locale automatically — noDateFormatterboilerplate required.
Variants
Wheel picker with a minimum age gate
struct BirthdayPickerView: View {
@State private var birthday = Calendar.current.date(
byAdding: .year, value: -18, to: Date()
) ?? Date()
// Latest allowed birthday: 18 years ago today
private var maxDate: Date {
Calendar.current.date(
byAdding: .year, value: -18, to: Date()
) ?? Date()
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Date of birth")
.font(.headline)
DatePicker(
"Birthday",
selection: $birthday,
in: ...maxDate, // one-sided range: past only
displayedComponents: [.date]
)
.datePickerStyle(.wheel)
.labelsHidden()
.accessibilityLabel("Date of birth")
}
.padding()
}
}
#Preview { BirthdayPickerView() }
Tinted graphical calendar
Apply .tint() to change the accent colour of the selected date circle and navigation chevrons in the graphical style — handy for matching your app's brand without any custom drawing:
DatePicker("Event date", selection: $eventDate,
displayedComponents: [.date])
.datePickerStyle(.graphical)
.tint(.indigo)
.labelsHidden()
Common pitfalls
-
⚠️ iOS version for
.graphical:.graphicalis available from iOS 14, but subtle layout bugs in iOS 16 cause the calendar to clip inside aFormsection. Always test on a real device or the iOS 17+ simulator, and consider wrapping it in a plainVStackrather than aFormrow if you see clipping. -
⚠️ Binding the same
Datefor date and time: If you use a singleDatebinding for both a[.date]picker and a[.hourAndMinute]picker, one will overwrite the other's component. Use a single@State varbut initialise it carefully, or merge two separate state values before submission. -
⚠️ Missing
.labelsHidden()insideForm: Without.labelsHidden(), the graphical and wheel styles render a duplicate label to the left of the control inside aForm. Add.labelsHidden()and provide a meaningful.accessibilityLabel()so VoiceOver users still get a description.
Prompt this with Claude Code
When using Soarias or Claude Code directly to implement this:
Implement a date picker in SwiftUI for iOS 17+. Use DatePicker with .compact and .graphical styles. Constrain the selectable range using a ClosedRange<Date>. Display a formatted summary below the picker using Date.FormatStyle. Make it accessible (VoiceOver labels on all pickers). Add a #Preview with realistic sample data.
In the Soarias Build phase, drop this prompt into a new screen scaffold so Claude Code generates the date picker alongside your existing form fields — keeping state management consistent with the rest of your view model.
Related
FAQ
Does this work on iOS 16?
Yes — DatePicker is available from iOS 13 and all three styles (.compact, .graphical, .wheel) work on iOS 16. However, the .graphical style has a known clipping bug inside Form on iOS 16.x. If you need to support iOS 16, wrap the graphical picker in a plain VStack or LazyVStack instead of a Form Section. Everything on this page targets iOS 17+ for the cleanest experience.
How do I show only specific times (e.g. every 30 minutes)?
SwiftUI's native DatePicker does not support a step interval for time selection — it allows any minute. For 15- or 30-minute intervals, the recommended approach is to replace the time picker with a Picker (wheel or menu style) populated with a computed array of Date values snapped to your desired interval. After selection, combine the chosen time with the date from your DatePicker using Calendar.current.dateComponents.
What is the UIKit equivalent?
The UIKit equivalent is UIDatePicker. You configure it with datePickerMode (.date, .time, .dateAndTime) and preferredDatePickerStyle (.compact, .inline, .wheels). In SwiftUI you'd only reach for UIDatePicker via a UIViewRepresentable wrapper if you need a feature not yet exposed — such as a custom locale override — but for virtually all use cases the native DatePicker view is the right choice on iOS 17+.
Last reviewed: 2026-05-11 by the Soarias team.