'SwiftUI View convenience initializers with `where` constraints

I have a generic SwiftUI view that imitates the pattern of a ForEach or List, as follows:

struct SwiftUIView<T: RandomAccessCollection, ElementView: View, ID: Hashable>: View {
    
    @Binding var selection: T.Element
    let keyPath: KeyPath<T.Element, ID>
    let content: (T.Element) -> ElementView
    var data: T
    
    init(data: T, id: KeyPath<T.Element, ID>, selection: Binding<T.Element>, @ViewBuilder content: @escaping (T.Element) -> ElementView) {
        ...
    }

    var body: some View {
        ForEach(data, id: keyPath) { element in 
           content(element)
        }
    }
}

and I'm trying to create some convenience initializers to leverage the indices of RandomAccessCollections.

The following initializer that maps my collection type T to the indices of some array (T == Array<Value>.Indices) compiles without issue.

init<Value>(data: [Value], selection: Binding<Array<Value>.Index>, @ViewBuilder content: @escaping (Array<Value>.Index, Value) -> ElementView)
where T == Array<Value>.Indices, ID == Array<Value>.Index {
    self.init(data: data.indices, id: \.self, selection: selection) { index in
        content(index, data[index])
    }
}

A similar initalizer which accepts a generic parameter C: RandomAccessCollection and maps T to C.Indices does not compile:

init<C: RandomAccessCollection>(data: C, selection: Binding<C.Index>, @ViewBuilder content: @escaping (C.Index, C.Element) -> ElementView)
where T == C.Indices, ID == C.Index {
    self.init(data: data.indices, id: \.self, selection: selection) { index in
        content(index, data[index])
    }
}

Note that the associated type Indices of a RandomAccessCollection also conforms to RandomAccessCollection.

On its face, it feels like this should work, as type C in the second initializer should act as a 1:1 stand-in for Array<Values> in the first initializer.

Wondering what I might be missing here, or if maybe the compiler is just not able to infer all the conformances here.



Solution 1:[1]

The SwiftUI.ForEach does not use such initilizer, instead they specify explicitly for Range<Int>... but I think you need something like (main part snapshot):

init(data: T, selection: Binding<T.Element>, @ViewBuilder content: @escaping (T.Element) -> ElementView) 
  where T.Element == ID {

Tested with Xcode 13.3 / iOS 15.4 on

Complete findings, code, and demo is here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Asperi