2

I am new in SwiftUI project where I need to implement a feature using MultiDatePicker that allows a user to select two dates only. Once a date are selected, all the dates which are comes in selected date range should be automatically selected.

Here I am attaching screen shot for better understanding.

GIF file

Screenshot

In above case user select from 27th December 2024 to 31th December 2024

So is it possible where the range of date between 27th and 31th December 2024 are getting selected automatically without select manually or tap on it. Expectation like

Screenshot

Thanks in advance

Code Edited

struct count: View {
    @State private var dates: Set<DateComponents> = []
    var datesBinding: Binding<Set<DateComponents>> {
        Binding {
            return dates
        } set: { newValue in
            self.dates = newValue
        }
    }

    var body: some View {
        return VStack(spacing: 50){
            MultiDatePicker("Select dates", selection: datesBinding)
                .frame(height: 300)
        }
        .padding()
    }
 }

Here I edit the where I am trying to return the range of dates but unfortunately i have no idea how to implement the expected functionality.

Adding GIF file for better understanding

GIF file

4
  • Try adding an .onChange callback to your MultiDatePicker. 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. Commented Dec 27, 2024 at 14:49
  • Please provide enough code so others can better understand or reproduce the problem. Commented Dec 27, 2024 at 17:53
  • @BenzyNeez: I added a sample of code but still have no idea how to implement, It would be grate if you help me on this. Commented Dec 30, 2024 at 9:54
  • @BenzyNeez : Added GIF file in bottom for expected result. kindly help on it. Commented Dec 30, 2024 at 11:16

1 Answer 1

6

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.

Animation

Sign up to request clarification or add additional context in comments.

6 Comments

Thank you so much for quick reply but in my case user can select only 2 dates and range automatically should be selected. when user is trying to select 3 day then previous selection should be removed and user have to select again to create range. adding gif link in my question(bottom) for better understanding. Kindly look into it.
@Bandish I revised the answer, hopefully it is closer to what you need now
Agree you gave me fantastic example of how we can apply .onChange event and as per your code i am not using surplusDates now, but i am stuck and not able to implement the logic in missingDates function to match my requirement.
Thank you so much, You save my time , Give you 1000+ :)
I got one issue .... if i select the date range more than a 5 months or 1 year then it is not working...i.e if i select 1 dec 2024 to 1 dec 2025 or 1 jun 2024 then it is not working...kindly help on this
|

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.