Given a recursive `struct Item` and KeyPath to the property `children: [Item]?`: from a Binding<[Item]>, how to get the binding to the `children` field?

Sorry for the confusing title. I hope my sample code is clear:

import Foundation
import SwiftUI

// A recursive struct
struct Item: Identifiable {
    var name: String
    var children: [Item]?    // with a KeyPath to this optional field, I need a binding to this when this is not nil

    let id = UUID()
}

// this code is simplified for asking this question
func recursiveView<Data>(@Binding data: Data, children: KeyPath<Data.Element, Data?>, rowContent: (Binding<Data.Element>) -> ()) where Data: MutableCollection, Data: RandomAccessCollection, Data.Index: Hashable, Data.Element: Identifiable {
    let index = data.indices.first!
    // from keyPath to Children, assume we already know the children property is not nil
    // need the binding, now the value!!!
    let bindingToChildren = $data[index][keyPath: children]!   // 👈👈👈 how to get the binding the children property (Binding<[Data.Element]>)?
    // 👆 the above doesn't compile
    // in my real code: error: Cannot convert value of type 'Any' to expected argument type 'Binding<Data>'
    // here: error: Key path of type 'KeyPath<Data.Element, Data?>' cannot be applied to a base of type 'Binding<Data.Element>'
}

struct Stub {
    @State var data = [
        Item(name: "Curly", children:[
            Item(name: "Dorothy")
        ])
    ]

    func body() {
        recursiveView($data: $data, children: \.children, rowContent: { $item in })
    }
}

children: KeyPath<Data.Element, Data?>

This is the core of your issue, the reason you're not getting a binding is because you're looking up member of type Data?, not Binding<[Data.Element]>

But the second issue is you're expecting a different type of the children property than it actually is:

how to get the binding the children property (Binding<[Data.Element]>)?

The type of the property children is not [Data.Element], it's just Data.

This compiles:

    struct Stub {
        @State var data = [
            Item(name: "Curly", children:[
                Item(name: "Dorothy")
            ])
        ]

        func body() {
            recursiveView(data: $data, children: \.children)
        }
        
        func recursiveView<Data>(
            data: Binding<Data>,
            children: KeyPath<Binding<Data.Element>, Binding<Data?>>
        ) where
            Data: MutableCollection,
            Data: RandomAccessCollection,
            Data.Index: Hashable,
            Data.Element: Identifiable
        {
            let index = data.wrappedValue.indices.first
            let foo = data[index!]
            let bindingToChildren: Binding<Data?> = foo[keyPath: children]
        }
    }

Note: recursiveView() uses SE-0293 property wrapper function parameter.

I need to call recursiveView(...) recursively. I change the signature and now can't get pass this compile error:

import Foundation
import SwiftUI

// A recursive struct
struct Item: Identifiable {
    var name: String
    var children: [Item]?

    let id = UUID()
}

// this code is simplified for asking this question
//                                      vvvvv <=== change this to match `bindingToChildren` below
func recursiveView<Data>(@Binding data: Data?, children: KeyPath<Binding<Data.Element>, Binding<Data?>>, rowContent: (Binding<Data.Element>) -> ()) where Data: MutableCollection, Data: RandomAccessCollection, Data.Index: Hashable, Data.Element: Identifiable {
    let index = data!.indices.first!

    // this is Binding<Data?>, is there anyway to get Binding<Data> so as to don't have to change the first param of `recursiveView(...)`?
    let bindingToChildren = $data[index][keyPath: children]   // 👈👈👈 error: Value of optional type 'Data?' must be unwrapped to refer to member 'subscript' of wrapped base type 'Data'
//                                      ^? 👈👈👈 unwrap here doesn't work
    recursiveView(data: bindingToChildren, children: children, rowContent: rowContent)
}

struct Stub {
    @State var data: [Item]? = [
        Item(name: "Curly", children:[
            Item(name: "Dorothy")
        ])
    ]

    func body() {
        recursiveView($data: $data, children: \.children, rowContent: { $item in })
    }
}
Terms of Service

Privacy Policy

Cookie Policy