'"View as a type cannot conform to the protocol itself" error when passing named closures to init @ViewBuilder
I have a custom View (DisclosableControl) that conditionally discloses a UI control.
In order to make this as adaptable as possible, the initialiser accepts closures which generate child views to display the result of user input and the control. As these child views are dependent on the state of the container, the closures that create them accept a generic Binding.
Passing closures in the init of DisclosableControl works well but it's very verbose and can make logic difficult to reason about so I wanted to define them in an extension to the root view either as a computed property or as the returned value from a function.
This works nicely in a Playground but to my surprise, inserting this code into my current project results in a compiler error inside the project ("View as a type cannot conform to the protocol itself").
In order to figure out what's going wrong, I pulled the working Playground code into Xcode with a view to "building up" to the point where the error arises. I didn't get a chance to do this though as there was an immediate compiler error relating to one of the computed properties: "Property definition has inferred type '(Binding) -> some View', involving the 'some' return type of another declaration"
Interestingly the other closure (that doesn't involve a UI control) seems to be happy.
Finally, attempts to explicitly define the type of the properties of the closure seem to make matters worse. The only way of getting the code to compile is to type erase the Picker associated with the 'control' property to AnyView. It works but I'd consider that a code smell.
Can anyone see where I'm going wrong?
DisclosableControl View
struct DisclosableControl<Value, Display: View, Control: View>: View {
@Environment(\.isEnabled) var isEnabled
@State private var isControlDisclosed = false
private var value: Binding<Value>
let title: LocalizedStringKey
let display: Display
let control: Control
init(
_ title: LocalizedStringKey,
value: Binding<Value>,
@ViewBuilder display: @escaping (Binding<Value>) -> Display,
@ViewBuilder control: @escaping (Binding<Value>) -> Control
) {
self.title = title
self.value = value
self.display = display(value)
self.control = control(value)
}
var body: some View {
List {
HStack {
display
.padding(.top, 15)
.overlay(alignment: .topLeading) {
Text(title)
.font(.caption)
.fixedSize()
.foregroundColor(
isEnabled ? Color.accentColor : Color(.systemGray)
)
}
Spacer()
}
.background()
.contentShape(Rectangle())
.onTapGesture {
withAnimation { isControlDisclosed.toggle() }
}
if (isControlDisclosed && isEnabled) {
control
.listRowSeparator(.hidden, edges: .top)
}
}
.animation(.easeInOut, value: isControlDisclosed)
}
}
Code used for testing (inside Preview)
enum TestEnum: String, CaseIterable {
case one, two, three
}
let control = { (value: Binding<TestEnum>) in
Picker("Picker", selection: value) {
ForEach(TestEnum.allCases, id: \.self) { value in
Text(value.rawValue)
}
}
.pickerStyle(SegmentedPickerStyle())
}
let display = { (value: Binding<TestEnum>) in
Text("The value selected was \(value.wrappedValue.rawValue)")
}
struct PlaygroundFile_Previews: PreviewProvider {
static var previews: some View {
Wrapper()
}
struct Wrapper: View {
@State private var value = TestEnum.one
var body: some View {
Form {
Text("Parent view state: \(value.rawValue)")
Disclosable(
"Disclosable Control",
value: $value,
display: display,
control: control)
}
}
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
