I’m trying to build an extension on a SwiftUI Picker, but I’m getting the following error inside the content closure when trying to compile:
Cannot convert return expression of type 'ForEach<Data, SelectionValue.ID, some View>' to return type 'Content'
Here’s the code:
extension Picker where SelectionValue: Identifiable, Label == Text {
init<Data>(_ title: String, selection: Binding<SelectionValue>, items: Data, @ViewBuilder itemContent: @escaping (Data.Element) -> some View) where Data: RandomAccessCollection, Data.Element == SelectionValue {
self.init(title, selection: selection, content: {
ForEach(items) { item in
itemContent(item).tag(item)
}
})
}
}
The ForEach
should conform to View, and therefore satisfy the Content type requirement, right?
Your init
is expected to be able to create Picker
s where the Content
type parameter is any View
, because you did not constrain its Content
to anything. However, the Picker
you create in init
has a Content
type of ForEach<...>
.
This means that you should add a constraint on Content
.
// note that you should also parameterise this with ItemContent
// you can't use (Data.Element) -> some View anymore, because you need the return type
// of that to constrain Content
init<Data, ItemContent>(
_ title: String,
selection: Binding<SelectionValue>,
items: Data,
@ViewBuilder itemContent: @escaping (Data.Element) -> ItemContent
) where
Data: RandomAccessCollection,
Data.Element == SelectionValue,
Content == ??? // <=== here
So what is the type of the ForEach(items) { ... }
? We don't actually know, because we don't know the type that the .tag
modifier returns.
One simple way to work around this is to create your own view that wraps this ForEach
:
struct PickerContentHelper<Data, Content>: View
where Data: RandomAccessCollection,
Data.Element: Hashable,
Data.Element: Identifiable,
Content: View
{
let items: Data
let itemContent: (Data.Element) -> Content
init(_ items: Data, @ViewBuilder itemContent: @escaping (Data.Element) -> Content) {
self.items = items
self.itemContent = itemContent
}
var body: some View {
ForEach(items) { item in
itemContent(item).tag(item)
}
}
}
You can then constrain Content
to be PickerContentHelper<Data, ItemContent>
:
extension Picker where SelectionValue: Identifiable, Label == Text {
init<Data, ItemContent>(
_ title: String,
selection: Binding<SelectionValue>,
items: Data,
@ViewBuilder itemContent: @escaping (Data.Element) -> ItemContent
) where
Data: RandomAccessCollection,
Data.Element == SelectionValue,
Content == PickerContentHelper<Data, ItemContent>
{
self.init(title, selection: selection) {
PickerContentHelper(items, itemContent: itemContent)
}
}
}
This is a common pattern seen in many other SwiftUI views. You can find many of these "helper views" in the "Supporting Types" section. For example, ShareLink
has DefaultShareLinkLabel
, and initialisers such as init(item:subject:message:)
constrain the Label
type parameter to be this type.