One way to respond to user interaction would be to add an .onChange callback to the date picker. However, using a computed binding (as you are doing in your example) is another way.
The advantage of using a computed binding is that changes to the set of DateComponents can be intercepted and modified, without the update causing a recursive call, as would be the case with .onChange. So using a computed binding is perhaps a better approach than using .onChange.
Here is how you might want to intercept changes to the set of dates:
If the new set of dates is empty, the change can be adopted. This would be the case when the user taps on the same date twice.
If the size of the date set has grown larger and there are now two dates, any gaps between the dates should be filled.
Otherwise, the date that was just added or just removed from the set should be identified and set as a single date. This becomes the start of a new range.
In order to convert between DateComponents and Date, it is important to use the same set of Calendar.Component as the picker is using. You will see from the post MultiDatePicker onChange not called if selection is set programmatically that this set consists of:
[.calendar, .era, .year, .month, .day]
So here is an example implementation that works as described above:
struct ContentView: View {
@Environment(\.calendar) var calendar
@State private var dates: Set<DateComponents> = []
let datePickerComponents: Set<Calendar.Component> = [.calendar, .era, .year, .month, .day]
var datesBinding: Binding<Set<DateComponents>> {
Binding {
dates
} set: { newValue in
if newValue.isEmpty {
dates = newValue
} else if newValue.count > dates.count {
if newValue.count == 1 {
dates = newValue
} else if newValue.count == 2 {
dates = filledRange(selectedDates: newValue)
} else if let firstMissingDate = newValue.subtracting(dates).first {
dates = [firstMissingDate]
} else {
dates = []
}
} else if let firstMissingDate = dates.subtracting(newValue).first {
dates = [firstMissingDate]
} else {
dates = []
}
}
}
var body: some View {
VStack(spacing: 50){
MultiDatePicker("Select dates", selection: datesBinding)
.frame(height: 300)
}
.padding()
}
private func filledRange(selectedDates: Set<DateComponents>) -> Set<DateComponents> {
let allDates = selectedDates.compactMap { calendar.date(from: $0) }
let sortedDates = allDates.sorted()
var datesToAdd = [DateComponents]()
if let first = sortedDates.first, let last = sortedDates.last {
var date = first
while date < last {
if let nextDate = calendar.date(byAdding: .day, value: 1, to: date) {
if !sortedDates.contains(nextDate) {
let dateComponents = calendar.dateComponents(datePickerComponents, from: nextDate)
datesToAdd.append(dateComponents)
}
date = nextDate
} else {
break
}
}
}
return selectedDates.union(datesToAdd)
}
}
In your animated gif, the range of dates was shown with a solid background. I think you would need to implement your own custom date picker to achieve this effect, the native MultiDatePicker always shows the selection as a set of individual dates.

.onChangecallback to yourMultiDatePicker. This then needs to fill the gaps between the first and last dates selected. If you are having difficulty getting this to work, please show the code of what you have so far.