15

I'm using a protocol to define an override to the default View protocol to create some templated views in my app. They will all share the same CalculationComponent view for the bulk of the layout but then will implement different buttons / controls that are passed through with a @ViewBuilder which uses generics.

The issue I'm having is that when defining my protocol body, the generic type is throwing an error where Type 'any View' cannot conform to 'View'. I think this has directly to do with the <Content: View> part on CalculationComponent

CalculationComponent.swift

struct CalculationComponent<Content: View>: View {
    @Binding var mainCalculation: String
    @Binding var secondaryCalculation: String
    @ViewBuilder var content: () -> Content
    
    var body: some View {
        // Template UI here

        content()
    }
}

CalculationView.swift

protocol CalculationView: View {
    var mainCalculation: String { get set }
    var secondaryCalculation: String { get set}
    var body: CalculationComponent<View> { get } // Type 'any View' cannot conform to 'View'
}

CalculatorView.swift

struct CalculatorView: CalculationView {
    @State internal var mainCalculation: String = ""
    @State internal var secondaryCalculation: String = ""
    
    var body: CalculationComponent {
        CalculationComponent(
            mainCalculation: $mainCalculation,  
            secondaryCalculation: $secondaryCalculation
        ) {
            Text("Buttons and view content here")
        }
    }
    
}

Joe Scotto
  • 10,936
  • 14
  • 66
  • 136
  • I had a much less sophisticated problem/solution for which I used this question. Gist is [here](https://gist.github.com/drosenstark/d90815b9c3ba83a90dbb4bd65a8d70ef). I'm sure I'll come back here with Google and wanted to leave a note ;) Thanks! – Dan Rosenstark Apr 28 '23 at 12:41

1 Answers1

10

If I understand correctly, you want a specialised version of the View protocol, where Body is CalculationComponent<some View>, and you don't want to write explicitly what "some View" is when conforming to the protocol, plus some other requirements.

You can add an associated type to CalculationView,

protocol CalculationView: View {
    associatedtype Content: View
    
    var mainCalculation: String { get set }
    var secondaryCalculation: String { get set}
    var body: CalculationComponent<Content> { get } // Type 'any View' cannot conform to 'View'
}

and then say CalculationComponent<some View> when conforming to the protocol:

struct CropFactorCalculatorView: CalculationView {
    
    @State internal var mainCalculation: String = ""
    @State internal var secondaryCalculation: String = ""
    
    var body: CalculationComponent<some View> {
        CalculationComponent(
            mainCalculation: $mainCalculation,
            secondaryCalculation: $secondaryCalculation
        ) {
            VStack {
                Text("Some Text")
                Text("Some Other Text")
            }
        }
    }
    
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • 2
    That works! Can you explain why I can't pass just `View` to the generic of `CalculationComponent`? Why does it have to be first defined as an `associatedtype`? – Joe Scotto Jul 17 '22 at 17:50
  • @JoeScotto See [this](https://stackoverflow.com/questions/33112559/protocol-doesnt-conform-to-itself). It would have worked if `View` didn't have the `Body` associated type requirement. It does have this associated type requirement, so `View` does not conform to `View`, and cannot be used to satisfy the `Content: View` constraint. After all, what is the `Body` type for `View`? You tell me! – Sweeper Jul 17 '22 at 17:56
  • I think I understand it now... basically `associatedtype Content: View` is saying that `Content` must be something that conforms to `View`. You can't just use View because that itself is a protocol and doesn't conform to anything therefore you must specify something that conforms to `View`. Does that sound about right? – Joe Scotto Jul 17 '22 at 18:27
  • 2
    @JoeScotto Yes. `View` doesn't conform to `View`. – Sweeper Jul 17 '22 at 18:43