I have a basic SwiftUI date picker that shows a calendar widget when tapped:
DatePicker(
"Date",
selection: $date,
in: ...Date(),
displayedComponents: [.date]
)

When you select a date (8th October in the example above), the calendar remains on screen and in order to collapse it, you need to tap outside of it.
Is it possible to automatically collapse it when a date is selected?
I ended up with a rather hacky solution that seems to be doing the job.
Add a @State variable that holds the calendar ID:
@State private var calendarId: Int = 0
Chain the DatePicker call with .id, .onChange and .onTapGesture actions:
DatePicker(
"Date", selection: $date, in: ...Date(), displayedComponents: [.date]
)
.id(calendarId)
.onChange(of: date) { _ in
calendarId += 1
})
Please note, this solution has a bug that will result in the calendar being dismissed when you change the month or year. See tier777's answer below for an iOS 17 solution.
In Xcode versions < 15 you might also need an .onTapGesture action on the above:
.onTapGesture {
calendarId += 1
}
But in Xcode 15 this seems to be causing the following crash:
This UITargetedPreview initializer requires that the view is in a window, but it is not. Either fix that, or use the other initializer that takes a target.
To solve this issue until SwiftUI brings some new update I prefer to use the following solution with .popOver view.
import SwiftUI
struct ContentView: View {
@State var selectedDate: Date = .now
@State var showCalendar = false
var body: some View {
VStack {
Button(action: {
print("Display size: \(UIScreen.main.bounds.width)") // For debuging purpose
showCalendar = true // Presenting the Calendar
}, label: {
Image(systemName: "calendar")
Text(selectedDate, style: .date)
})
.popover(isPresented: $showCalendar) {
DatePicker(
"Select date",
selection: $selectedDate,
displayedComponents: .date
)
.datePickerStyle(.graphical)
.padding()
.frame(width: 365, height: 365) // Check the explanation part
.presentationCompactAdaptation(.popover) // It will show popOver in iPhones too
}
.onChange(of: selectedDate) { _, _ in
showCalendar = false // Dismisses the calendar
}
Spacer()
}
}
}
Explanaiton of frame(width: 365, height: 365)
Understanding this part is very important to use this solution. The smallest device I want to support for my app is IPhone SE (3rd Gen), which has screen width 375 (You can print as I did in the button action, please check)
So If I want my calendar to fit in the smallest device I made the frame size height and width equals to 365. Also the calendar view itself is of shape square so the height and width should be equal (I don't have any prove for this, it is my observation)
Note: Where you show the calendar you need to make sure that there is enough space in height and width to show the calendar view frame size (365 in my case). In most of the cases you don't need to worry about these things, these corner cases may arise only in small devices.
Output simulation video

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With