skip to Main Content

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()
    }
}

iPhone Screenvideo

Am I doing something wrong or is it just not possible to select a date in the MultiDatePicker programmatically?

Thank You!

2

Answers


  1. 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()
        }
    }
    
    Login or Signup to reply.
  2. 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)
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search