1

Premise


I have a MultiDatePicker bounded to a closed range of the current month. In addition, I have a datePicker representing a "Start Date" that is the current day. I want the Day of the Month from "Start Date" to be pre-selected in the MultiDatePicker.


Example Code

import SwiftUI
import SwiftData

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    
    @State private var days: Set<DateComponents> = []

    var body: some View {
        VStack {
            
            Text("Selected Days")
            
            let dayArray = Array(days)
            ForEach(dayArray, id: \.self) { day in
                Text(Calendar.current.date(from: day)?.formatted(date: .complete, time: .complete) ?? "")
                
            }
            
            MultiDatePicker("", selection: $days)
        }
    }
}
    
#Preview {
    ContentView()
        .modelContainer(for: Item.self, inMemory: true)
}

Attempted Solution

I have attempted to add a DateComponent in the initial value for days, like this

@State private var days: Set<DateComponents> = [DateComponents(year: 2025, month: 11, day: 19)]

(In my fully-implemented version, I use values from "Start Date" and the current Date. From testing, hard-coding the date did not seem to affect my outcome. They are hard-coded here for simplicity)

Problem: For some reason, I am unable to remove this default option from the picker. When de-selecting the current date from the picker,

  1. The date never leaves the list of selected dates
  2. The date re-appears when closing the picker and re-opening it
  3. The de-selection doesn't call a .onChange(of: days) {...}

Observation: In my fully implemented version of this, I've found I'm able to double-select the pre-selected date. I do this by first de-selecting (which doesn't remove it), then re-selecting it (which adds the second instance).

This gave me the suspicion that, at times, I'm storing 2 instances of DateComponents with the same date values, but different time values.

After printing all DateComponents stored, I observed that the two instances of DateComponent sharing that Day of the Month are identical in date and time down to the second.

1

1 Answer 1

1

The issue is that MultiDatePicker doesn’t treat your custom DateComponents as the same values it creates internally. If you insert a DateComponents with only year/month/day, it won’t match what the picker generates when the user taps a date, so deselection never removes it.

MultiDatePicker uses a very specific set of components, including calendar and era. Because your preset components don’t have them, the picker thinks these are two different dates.

If you create the default value using the same component set, everything starts to work normally. For example:

let today = Calendar.current.dateComponents(
    [.calendar, .era, .year, .month, .day],
    from: Date()
)
days.insert(today)

Once you include those extra fields, the picker can properly match, remove, and update the selection, and onChange will fire as expected.

struct ContentView: View {

    @State private var days: Set<DateComponents> = []

    var body: some View {
        VStack {
            MultiDatePicker("Dates", selection: $days)
                .onAppear {
                    preselectToday()
                }

            ForEach(Array(days), id: \.self) { dc in
                Text(Calendar.current.date(from: dc)?.formatted() ?? "")
            }
        }
    }

    private func preselectToday() {
        let cal = Calendar.current
        let today = cal.dateComponents(
            [.calendar, .era, .year, .month, .day],
            from: Date()
        )
        days.insert(today)
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.