Skip to content

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 DatePicker to a @State variable so the UI updates automatically when the user picks a date
  • How to restrict the selectable range using a ClosedRange of Date values built from Calendar and DateComponents
  • How to switch between CompactDatePickerStyle, GraphicalDatePickerStyle, and WheelDatePickerStyle
  • How to format a Date for human-readable display using DateFormatter

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

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

  1. @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 at Date() means the picker opens showing today.

  2. startingDate and endingDate — These two let constants 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.

  3. dateFormatter computed property — A DateFormatter configured 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.

  4. Text(dateFormatter.string(from: selectedDate)) — Reads selectedDate at render time and converts it to a human-readable string. Because selectedDate is @State, this text automatically stays in sync with what the picker shows.

  5. DatePicker("Select a date", selection: $selectedDate, in: startingDate...endingDate, displayedComponents: [.date]) — The four-argument form. The label is used for accessibility (VoiceOver reads it), $selectedDate is the two-way binding, in: restricts the range, and displayedComponents: [.date] hides the time segment entirely.

  6. .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; tint is the modern replacement.

  7. Commented-out picker style variantsGraphicalDatePickerStyle() 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 render
DateFormatter 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 DatePicker to a @State var selectedDate: Date — SwiftUI handles all re-rendering automatically when the user changes the value.
  • Use in: startingDate...endingDate to enforce valid date ranges at the UI level, preventing impossible or out-of-policy selections.
  • Choose your style (CompactDatePickerStyle, GraphicalDatePickerStyle, or WheelDatePickerStyle) based on available screen space and how central the date selection is to the screen's purpose.

Last updated: June 27, 2026

Released under the MIT License.