Search code examples
iosswiftxcodegenericsswiftui

Type 'any View' cannot conform to 'View' on protocol with generics


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")
        }
    }
    
}


Solution

  • 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 }
    }
    

    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")
                }
            }
        }
        
    }