iOS14 SwiftUI DatePicker - How to display inline like calendar and reminders apps

I have a Form containing DatePickers, such as:
Code Block swift
DatePicker("Date of Birth", selection: $child.dob, displayedComponents: .date)

When I run this, the date picker is displayed modally. How do I get it to expand inline in the form? I can't see any obvious way to do it.
Answered by OOPer in 615726022
Thanks for showing your code. Now I understand what you described.

in iOS 13, the DatePicker expands inline when the date value is touched. in iOS 14, instead of expanding inline, it displays modally instead.

One good way, is that accepting the default style as is. The default style may be the most frequently used style, that users of iOS 13 might be accustomed to the default style of iOS 13, users of iOS 14 to the style of iOS 14.

Or else, you can try datePickerStyle, as suggested by mtsrodrigues.
But you need to care, that actual style and behavior depends on:
  • iOS versions (as you have found)

  • Whether the DatePicker is contained in a Form or not

There may be others, so you should better test the effect with your actual view design.

If any styles does not provide the style and behavior in iOS 13 & 14 as you expect, you may need to write it yourself.
Code Block
    @State var showsDatePicker = false
    let dateFormatter: DateFormatter = {
        let df = DateFormatter()
        df.dateStyle = .medium
        return df
    }()
    var body: some View {
        NavigationView {
            Form {
                TextField("Name", text: $child.name)
                HStack {
                    Text("Date of Birth")
                    Spacer()
                    Text("\(dateFormatter.string(from: child.dob))")
                        .onTapGesture {
                            self.showsDatePicker.toggle()
                        }
                        .padding(EdgeInsets(top: 2, leading: 10, bottom: 2, trailing: 10))
                        .background(showsDatePicker
                                        ? Color.clear
                                        : Color.gray)
                }
                if showsDatePicker {
                    DatePicker("", selection: $child.dob, displayedComponents: .date)
                        .datePickerStyle(WheelDatePickerStyle())
                }
            }
            .navigationBarTitle("Add Child", displayMode: .inline)
            .navigationBarItems(leading: Button(action: self.onCancel) { Text("Cancel") }, trailing: Button(action: self.onSave) { Text("Save") })
        }
    }

You may need some time to refine it, and may need more time when iOS 15 is out...
So, I'm not sure this would be the best solution for you.
Please show more context. The behavior may be affected by outer environment.
Sure. The surrounding body looks like this:
Code Block swift
    var body: some View {
        NavigationView {
            Form {
                TextField("Name", text: $child.name)
                DatePicker("Date of Birth", selection: $child.dob, displayedComponents: .date)
            }
            .navigationBarTitle("Add Child", displayMode: .inline)
            .navigationBarItems(leading: Button(action: self.onCancel) { Text("Cancel") }, trailing: Button(action: self.onSave) { Text("Save") })
        }
    }

The view is displayed in a sheet. in iOS 13, the DatePicker expands inline when the date value is touched. in iOS 14, instead of expanding inline, it displays modally instead.

You can use the datePickerStyle modifier to set the style for a DatePicker as following:

Code Block swift
struct ContentView: View {
    @State private var birthdate = Date()
    var body: some View {
        DatePicker("Date of Birth", selection: $birthdate, displayedComponents: .date)
            .datePickerStyle(WheelDatePickerStyle())
    } 
}


DefaultDatePickerStyle (iOS, macOS)
The default DatePicker style.

WheelDatePickerStyle (iOS)
A system style of date picker that displays each component as columns in a scrollable wheel.

FieldDatePickerStyle (macOS)
A system style that displays the components in an editable field.

GraphicalDatePickerStyle (iOS)
A system style of DatePicker that displays an interactive calendar or clock.

StepperFieldDatePickerStyle (macOS)
A system style that displays the components in an editable field, with adjoining stepper that can increment/decrement the selected component.
.datePickerStyle() doesn't alter whether the calendar is displayed inline or modally in iOS 14. I tired that and what it changes is how the date picker display is rendered (wheel/graphical/default). Please, test it yourself in iOS 14 - I can't upload a screenshot, but in my code, the new (iOS 14 calendar style) date picker is displayed in a modal view above a blurred overlay covering the rest of the screen. In the Apple apps, it expands beneath the row.
Accepted Answer
Thanks for showing your code. Now I understand what you described.

in iOS 13, the DatePicker expands inline when the date value is touched. in iOS 14, instead of expanding inline, it displays modally instead.

One good way, is that accepting the default style as is. The default style may be the most frequently used style, that users of iOS 13 might be accustomed to the default style of iOS 13, users of iOS 14 to the style of iOS 14.

Or else, you can try datePickerStyle, as suggested by mtsrodrigues.
But you need to care, that actual style and behavior depends on:
  • iOS versions (as you have found)

  • Whether the DatePicker is contained in a Form or not

There may be others, so you should better test the effect with your actual view design.

If any styles does not provide the style and behavior in iOS 13 & 14 as you expect, you may need to write it yourself.
Code Block
    @State var showsDatePicker = false
    let dateFormatter: DateFormatter = {
        let df = DateFormatter()
        df.dateStyle = .medium
        return df
    }()
    var body: some View {
        NavigationView {
            Form {
                TextField("Name", text: $child.name)
                HStack {
                    Text("Date of Birth")
                    Spacer()
                    Text("\(dateFormatter.string(from: child.dob))")
                        .onTapGesture {
                            self.showsDatePicker.toggle()
                        }
                        .padding(EdgeInsets(top: 2, leading: 10, bottom: 2, trailing: 10))
                        .background(showsDatePicker
                                        ? Color.clear
                                        : Color.gray)
                }
                if showsDatePicker {
                    DatePicker("", selection: $child.dob, displayedComponents: .date)
                        .datePickerStyle(WheelDatePickerStyle())
                }
            }
            .navigationBarTitle("Add Child", displayMode: .inline)
            .navigationBarItems(leading: Button(action: self.onCancel) { Text("Cancel") }, trailing: Button(action: self.onSave) { Text("Save") })
        }
    }

You may need some time to refine it, and may need more time when iOS 15 is out...
So, I'm not sure this would be the best solution for you.
OK I think I've figured it out (but haven't proven it yet). I just watched the "Stacks, Grids and Outlines in SwiftUI" WWDC20 video and I think the Apple apps are using a DisclosureGroup to display the DatePicker inline in a Form. Will try it tomorrow and report back.
@andrewjackman

The suggestion by @OOPer will work. You could also use a DisclosureGroup, but it will display a disclosure indicator, and I don't see how to suppress that.

GraphicalDatePickerStyle is what you want to choose if you're trying to replicate the inline calendar style display that is seen in the Calendar app.

Unfortunately, the DatePicker does not seem to size itself correctly when using the GraphicalDatePickerStyle. I tried it in a List, a Form, and a ScrollView, and it has the same problem in all of them. By default, it just gets compressed to a tiny, unusable size. The only way I could find to get it to display correctly was to *manually* provide a minHeight value for it. That is, of course, a terrible solution since that value will be different based on the container's width and the current trait collection.

I tried calculating the minHeight value using a static UIDatePicker, calling sizeToFit on it. This worked pretty well for calculating the required height of a DatePicker with displayedComponents set to [.date]. Unfortunately, it doesn't calculate the correct height when using the [.hourAndMinute] or [.date, .hourAndMinute] configuration, it doesn't provide enough height for the time related views. I also tried the static UIDatePicker's systemLayoutSizeFittingSize: by passing .layoutFittingCompressedSize to it, but this did not return an accurate height at all. Maybe there's a better way to calculate it, but we shouldn't have to calculate it at all.

Even when providing a minHeight, I see log statements while debugging due to the DatePicker's generated auto layout constraints being unsatisfiable. I believe all this sizing stuff is a bug. I'll be submitting a feedback report about it today.
Thanks @tundaware - yeah the GraphicalDatePickerStyle is completely broken, and I dismissed it because of that, but now I see your point. You are correct that the DisclosureGroup didn't help in any way. I'll implement @OOPer 's solution, and hopefully the sizing issue will be fixed in the next beta.
iOS14 SwiftUI DatePicker - How to display inline like calendar and reminders apps
 
 
Q