I try to select a date (today) in a MultiDatePicker programmatically via a Button. The selection works as expected and the onChange modifier will be called and the day today is marked as selected in the Picker. But when I try to deselect the date directly in the MultiDatePicker, the date is not marked anymore but the onChange modifier will not get called. If you tap on another date in the MultiDatePicker now, both dates, the other date and the date today are marked as selected.
import SwiftUI
struct ContentView: View {
@Environment(\.calendar) private var calendar
@State private var selectedDates: Set<DateComponents> = []
@State private var onChangeCounter = 0
var body: some View {
VStack {
MultiDatePicker("Select dates", selection: $selectedDates)
.frame(height: UIScreen.main.bounds.width)
.onChange(of: selectedDates) { _ in
self.onChangeCounter = 1
}
Button("Select today") {
let todayDatecomponents = calendar.dateComponents(in: calendar.timeZone, from: Date.now)
selectedDates.insert(todayDatecomponents)
}
.foregroundColor(.white)
.frame(minWidth: 150)
.padding()
.background(Color.accentColor)
.cornerRadius(20)
HStack {
Text("onChangeCounter")
Spacer()
Text(String(onChangeCounter))
}
.padding()
Spacer()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Am I doing something wrong or is it just not possible to select a date in the MultiDatePicker programmatically?
Thank You!
CodePudding user response:
For purposes of this discussion, Today means December 17, 2022
The issue is that Date.now
is not equal to Today
I'm in US East Coast Time Zone... if I add a button to print(Date.now)
and tap it, I see this in the debug console:
2022-12-17 14:08:52 0000
if I tap it again 4-seconds later, I see this:
2022-12-17 14:08:56 0000
Those two dates are not equal.
So, let's find out what the MultiDatePicker
is using for it's selection
.
Change your MultiDatePicker
to this:
MultiDatePicker("Select dates", selection: $selectedDates)
.frame(height: UIScreen.main.bounds.width)
.onChange(of: selectedDates) { _ in
print("onChange")
selectedDates.forEach { d in
print(d)
}
print()
self.onChangeCounter = 1
}
If I run the app and select Dec 14, 19 and 8, I see this:
onChange
calendar: gregorian (current) era: 1 year: 2022 month: 12 day: 14 isLeapMonth: false
onChange
calendar: gregorian (current) era: 1 year: 2022 month: 12 day: 14 isLeapMonth: false
calendar: gregorian (current) era: 1 year: 2022 month: 12 day: 19 isLeapMonth: false
onChange
calendar: gregorian (current) era: 1 year: 2022 month: 12 day: 14 isLeapMonth: false
calendar: gregorian (current) era: 1 year: 2022 month: 12 day: 19 isLeapMonth: false
calendar: gregorian (current) era: 1 year: 2022 month: 12 day: 8 isLeapMonth: false
Now, I de-select the 19th, and I see this:
onChange
calendar: gregorian (current) era: 1 year: 2022 month: 12 day: 14 isLeapMonth: false
calendar: gregorian (current) era: 1 year: 2022 month: 12 day: 8 isLeapMonth: false
The 19th was correctly removed from the Set
.
Now, I tap your "Select today" button, and I see this:
onChange
calendar: gregorian (current) era: 1 year: 2022 month: 12 day: 14 isLeapMonth: false
calendar: gregorian (current) era: 1 year: 2022 month: 12 day: 8 isLeapMonth: false
calendar: gregorian (current) timeZone: America/New_York (fixed (equal to current)) era: 1 year: 2022 month: 12 day: 17 hour: 9 minute: 24 second: 11 nanosecond: 339460015 weekday: 7 weekdayOrdinal: 3 quarter: 0 weekOfMonth: 3 weekOfYear: 51 yearForWeekOfYear: 2022 isLeapMonth: false
As we can see, these two lines:
let todayDatecomponents = calendar.dateComponents(in: calendar.timeZone, from: Date.now)
selectedDates.insert(todayDatecomponents)
Insert a DateComponents
object with a lot more detail.
If I tap "Select today" again, I get this:
onChange
calendar: gregorian (current) era: 1 year: 2022 month: 12 day: 8 isLeapMonth: false
calendar: gregorian (current) era: 1 year: 2022 month: 12 day: 14 isLeapMonth: false
calendar: gregorian (current) timeZone: America/New_York (fixed (equal to current)) era: 1 year: 2022 month: 12 day: 17 hour: 9 minute: 24 second: 11 nanosecond: 339460015 weekday: 7 weekdayOrdinal: 3 quarter: 0 weekOfMonth: 3 weekOfYear: 51 yearForWeekOfYear: 2022 isLeapMonth: false
calendar: gregorian (current) timeZone: America/New_York (fixed (equal to current)) era: 1 year: 2022 month: 12 day: 17 hour: 9 minute: 26 second: 39 nanosecond: 866878032 weekday: 7 weekdayOrdinal: 3 quarter: 0 weekOfMonth: 3 weekOfYear: 51 yearForWeekOfYear: 2022 isLeapMonth: false
Now selectedDates
contains two "today dates" ... 2-minutes and 28-seconds apart.
When I tap the 17th on the calendar, there is no matching date in that set
to remove... so when the calendar refreshes (such as when I select another date), the 17th still shows as selected.
So, let's change the programmatically inserted DateComponents
to match the calendar's data:
let todayDatecomponents = calendar.dateComponents([.calendar, .era, .year, .month, .day], from: Date.now)
selectedDates.insert(todayDatecomponents)
Now when we tap 17
on the calendar it will be de-selected and the matching object selectedDates
will be removed.
Here's how I modified your code to debug:
import SwiftUI
@available(iOS 16.0, *)
struct MultiDateView: View {
@Environment(\.calendar) private var calendar
@State private var selectedDates: Set<DateComponents> = []
@State private var onChangeCounter = 0
var body: some View {
VStack {
MultiDatePicker("Select dates", selection: $selectedDates)
.frame(height: UIScreen.main.bounds.width)
.onChange(of: selectedDates) { _ in
print("onChange")
selectedDates.forEach { d in
print(d)
}
print()
self.onChangeCounter = 1
}
Button("Select today") {
let todayDatecomponents = calendar.dateComponents(in: calendar.timeZone, from: Date.now)
selectedDates.insert(todayDatecomponents)
}
.foregroundColor(.white)
.frame(minWidth: 150)
.padding()
.background(Color.accentColor)
.cornerRadius(20)
Button("Select today the right way") {
let todayDatecomponents = calendar.dateComponents([.calendar, .era, .year, .month, .day], from: Date.now)
selectedDates.insert(todayDatecomponents)
}
.foregroundColor(.white)
.frame(minWidth: 150)
.padding()
.background(Color.green)
.cornerRadius(20)
HStack {
Text("onChangeCounter")
Spacer()
Text(String(onChangeCounter))
}
.padding()
Button("Print Date.now in debug console") {
print("debug")
print("debug:", Date.now)
print()
}
.foregroundColor(.white)
.frame(minWidth: 150)
.padding()
.background(Color.red)
.cornerRadius(20)
Spacer()
}
}
}
@available(iOS 16.0, *)
struct MultiDateView_Previews: PreviewProvider {
static var previews: some View {
MultiDateView()
}
}
CodePudding user response:
it looks like MultiDatePicker
is saving additional components.
Use this to set the today components:
let todayDatecomponents = calendar.dateComponents([.calendar, .era, .year, .month, .day], from: .now)