Type any Protocol cannot conform to Protocol

3k views Asked by At

I am facing an issue when trying to initialize a view in SwiftUI. Let me explain:

I have a view called ExternalView, this view has a constraint with a protocol called ToggleableProtocol, then, this view internally consumes a view called InternalView, which is a view that has a constraint to a Hashable protocol.

The error occurs when I try to create an instance of ExternalView and pass it an array of different structs that conform to the ToggleableItem protocol. The error says Type 'any TogleableItem' cannot conform to 'TogleableItem'

TogleableItem

public protocol TogleableItem: Hashable {
    var text: String { get }
    var isSelected: Bool { get set }
}

Structs conforming to the TogleableItem

struct FirstObject: TogleableItem {
    var text = "FirstItem"
    var isSelected = false
}

struct SecondObject: TogleableItem {
    var text = "SecondItem"
    var isSelected = false
}

ContentView

struct ContentView: View {
    @State private var items: [any TogleableItem] = [
        FirstObject(),
        SecondObject()
    ]
    
    var body: some View {
        ExternalView(
            items: items
        ) { isSelected, index, _ in
            items[index].isSelected = isSelected
        }
    }
}

ExternalView

public struct ExternalView<T: TogleableItem>: View {
    private let items: [T]
    private let onItemTap: (Bool, Int, T) -> Void
    
    public init(
        items: [T],
        onItemTap: @escaping (Bool, Int, T) -> Void
    ) {
        self.items = items
        self.onItemTap = onItemTap
    }
    
    public var body: some View {
        InternalView(
            items: items
        ) { index, element in
            Text(element.text)
                .foregroundColor(element.isSelected ? .red : .blue)
        }
    }
}

InternalView

struct InternalView<Element: Hashable, Content: View>: View {
    private let items: [Element]
    private let content: (Int, Element) -> Content
    
    init(
        items: [Element],
        content: @escaping (Int, Element) -> Content
    ) {
        self.items = items
        self.content = content
    }
    
    var body: some View {
        LazyHStack(spacing: 0) {
            ForEach(items.indices, id: \.self) { index in
                content(index, items[index])
            }
        }
    }
}

enter image description here

Thanks!!

I have tried changing the items parameter, inside the ExternalView to something like [any TogleableItem] but in the end it causes a similar error when consuming the InternalView

1

There are 1 answers

3
Joakim Danielson On

The problem with your code is that you define for instance ExternalView as generic

ExternalView<T: TogleableItem>

then you say that T can be of a (read one) type that conforms to TogleableItem but you want to use the view for a mix of types that conforms to TogleableItem.

The solution as I see it is to not make the view types generics and instead use TogleableItem directly in the declarations (I have skipped a lot of code below for brevity)

public struct ExternalView: View {
    private let items: [any TogleableItem]
    private let onItemTap: (Bool, Int, any TogleableItem) -> Void
    ...
}

struct InternalView<Content: View>: View {
    private let items: [any TogleableItem]
    private let content: (Int, any TogleableItem) -> Content
    ...
}

Another way to solve it if InternalView should be able to use other types than those conforming to TogleableItem is to use the original solution but without Element conforming to Hashable

struct InternalView<Element, Content: View>: View {
    private let items: [Element]
    private let content: (Int, Element) -> Content