Is there a way for third-party UI libraries to use `SwiftUI.ForEach` in result builders?

I wanted to provide some api that works like SwiftUI.ForEach so that others can use my library like this:

protocol GraphContent {
    associatedtype Body: GraphContent
    @GraphContentBuilder
    var graph: Body { get }
}

struct MyGraph: GraphContent {
    var graph: some GraphContent {
        ForEach([0, 3, 5], id: \.self) { i in
            Node(id: i)
        }
        Link(fromId: 0, toId: 3)
    }
}

But I can find a way to initialize ForEach<Array, ID, some MyOwnProtocol>. For now the only way I can think of is to use some pullback and utilize the initializer ForEach<Array, ID, some SwiftUI.View> like this:

struct ForEachGraphContentWrapper<Content> where Content: GraphContent {
    let content: Content
}

extension ForEachGraphContentWrapper: View {
    var body: Never {
        fatalError()
    }
}

extension ForEachGraphContentWrapper {
    static func pullback<Element, ID>(_ id: KeyPath<Element, ID>, _ graphContent: @escaping (ID) -> Content) -> ((Element) -> Self) {
        return { element in
            ForEachGraphContentWrapper(content: graphContent(element[keyPath: id]))
        }
    }
}

extension ForEach where Content: View {

    init<GC: GraphContent>(
        _ data: Data, 
        id: KeyPath<Data.Element, ID>,
        @GraphContentBuilder<ID> content: @escaping (ID) -> GC
    ) where Content == ForEachGraphContentWrapper<GC>, ID: Hashable {
        let pb = ForEachGraphContentWrapper.pullback(id, content)
        // convert my own content builder to view builder, and use the initializer for view
        self.init(data, id: id, content: pb)
    }
}

This is quite ugly and since there's some non-SwiftUI framework from Apple using ForEach, is it possible to initialize ForEach without adding View conformance?

You can’t use ForEach in general because, although ForEach exposes content and data properties, it doesn’t expose an id property. If you want to limit yourself to only Identifiable elements, you might be able to make it work.

there's some non-SwiftUI framework from Apple using ForEach

The Charts framework probably uses _spi to access SwiftUI features that we on the outside can’t.

1 Like

Depending on how GraphContentBuilder is implemented, you could implement buildArray and use native for .. in loops. Your example could look like this then:

struct MyGraph: GraphContent {
    var graph: some GraphContent {
        for i in [0, 3, 5] {
            Node(id: i)
        }
        Link(fromId: 0, toId: 3)
    }
}
1 Like