Reputation: 1671
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?
Upvotes: 1
Views: 86
Reputation: 273540
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.
Upvotes: 1
Reputation: 1
The error you're encountering suggests that the return type of the ForEach expression inside the content closure doesn't match the expected return type of the content closure itself modify the extension to ensure that the return type is compatible
extension Picker where SelectionValue: Identifiable, Label == Text {
init<Data>(_ title: String, selection: Binding
Upvotes: -3