How to do `.datePickerStyle(isGraphical ? GraphicalDatePickerStyle() : WheelDatePickerStyle())`?

// this doesn't compile :(
// somehow make a `AnyDatePickerStyle`?
DatePicker("Pick a date", selection: $date)
    .labelsHidden()
    // error: Result values in '? :' expression have mismatching types 'GraphicalDatePickerStyle' and 'WheelDatePickerStyle'
    .datePickerStyle(isGraphical ? GraphicalDatePickerStyle() : WheelDatePickerStyle())

so have to do this:

// this works but prefer not to "swaps views out"
if isGraphical {
    DatePicker("Pick a date", selection: $date)
        .labelsHidden()
        .datePickerStyle(GraphicalDatePickerStyle())
} else {
    DatePicker("Pick a date", selection: $date)
        .labelsHidden()
        .datePickerStyle(WheelDatePickerStyle())
}

but would rather not swaps views out like this and the code doesn't look right: it's not obvious why it's this way.

Initially SwiftUI control styles like DatePickerStyle are enum's, so you can just pass in a different enum value to .datePickerStyle(:). But later they changed all the styles to be protocol values. So it's not possible to dynamically change style by passing different value to .xxxStyle(:) modifiers. Wonder what's the reason for this change?

Edit:

Initially SwiftUI control styles like DatePickerStyle are enum's

I mis-remember, it's was StaticMember, see: [Proposal] Static member lookup on protocol metatypes

The Style protocols work by passing you an underlying view and you wrapping it into another view context.

If there was an API to dynamically switch between two styles, it would still need to remove the old hierarchy that came from the decoration of the first style, and insert a new hierarchy from the decoration of the second style.

I think the if/else approach is the only thing that works here.

2 Likes

Ok, thanks! I'll pretty it up:

DatePicker("Pick a date", selection: $date)
    .labelsHidden()
    .if(isGraphical) { $0.datePickerStyle(GraphicalDatePickerStyle()) } else: { $0.datePickerStyle(WheelDatePickerStyle()) }

I had the same problem with .foregroundStyle and fixed it using AnyShapeStyle, "A type-erased ShapeStyle value." as follows:

.foregroundStyle(isFocused ? AnyShapeStyle(.foreground) : AnyShapeStyle(.tint))

There currently is no AnyDatePickerStyle to do similar type-erasure but you can implement it yourself as:

public struct AnyDatePickerStyle: DatePickerStyle {
    private let _makeBody: (Configuration) -> AnyView
    
    public init<S: DatePickerStyle>(_ style: S) {
        self._makeBody = { config in
            AnyView(style.makeBody(configuration: config))
        }
    }
    
    public func makeBody(configuration: Configuration) -> some View {
        _makeBody(configuration)
    }
}

Now using it in your sample code like this fixes the compilation error:

struct ContentView: View {
    
    @State var isGraphical = false
    @State var date = Date()
    
    var body: some View {
        NavigationStack {
            VStack {
                Toggle("isGraphical", isOn: $isGraphical)
                
                DatePicker("Pick a date", selection: $date)
                    .labelsHidden()
                    .datePickerStyle(isGraphical ? AnyDatePickerStyle(GraphicalDatePickerStyle()) : AnyDatePickerStyle(WheelDatePickerStyle()))
            }
        }
    }
}

They say AnyView is inefficient but not sure how bad it is in this case.

1 Like