I want a simple convenience initialiser for Picker
extension Picker {
nonisolated public init(selection: Binding<SelectionValue>, @ViewBuilder content: () -> Content) {
self.init(selection: selection, content: content) {
EmptyView()
}
}
}
The compiler complains.
Cannot convert return expression of type 'EmptyView' to return type 'Label'
But actually, the Label I need to return is just View.
@MainActor @preconcurrency
struct Picker<Label, SelectionValue, Content> where Label : View, SelectionValue : Hashable, Content : View
(and this code works and compiles if use it in a concrete way.
So - apart from the confusion of using Label both as a generic placeholder, and also a concrete type;
Is there a way I can express this intent?
j-f1
(Jed Fox)
2
You need to constrain the type of the Label generic parameter:
extension Picker {
nonisolated public init(selection: Binding<SelectionValue>, @ViewBuilder content: () -> Content) where Label == EmptyView {
self.init(selection: selection, content: content) {
EmptyView()
}
}
}
I suspect if you tried to call the initializer as you had it written you would’ve gotten an error because the compiler couldn’t infer the type of the Label type.
A little off topic, but in SwiftUI, the label of a control like Picker is used to help people using accessibility tools understand what the control does. You may want to instead leave the label in and use the .labelsHidden() modifier to hide it visually.
2 Likes
Thanks. I incorrectly tried constraining to Label:View which didn't work...
The error was
Cannot convert return expression of type 'EmptyView' to return type 'Label'
which confused me because I was thinking that EmptyView is a Label - in the sense that Label is any View (rather than the concrete type Label)
Generics are tricky...
And you're also right that labelIsHidden is better!
j-f1
(Jed Fox)
4
The reason you have to be so explicit is that the caller chooses the concrete types of the different generic type names. So if the user explicitly specified a Picker with the Label type set to Circle, there wouldn’t be a way for your initializer to pass a view of the appropriate type to the SwiftUI-provided initializer. But if you constrain the Label type to a specific concrete type in the where clause, the user can only call your initializer if they set the Label type to that specific type (or let Swift infer it for them). That said, it may make sense to propose some sort of language addition that would allow you to spell “I want the Label type to be some View that is defined by the initializer and cannot be explicitly named outside of the initializer.”
Certainly not a biggie for me - but I expected a new initialiser to essentially work within the constraints set elsewhere by the struct.
The current syntax is kind of backwards. It reads like
Offer this initialiser when the label is EmptyView
but it implements
I'm setting the label to be EmptyView when you use this initialiser
so, the caller really isn't setting the label type - other than by a process of elimination.