recursionswiftuidisclosuregroup

Recursive Disclosure Groups in SwiftUI


I would like to show a directory structure with all nodes expanded in SwiftUI. I found an example of a recursive disclosure view structure on the internet.

The view:

struct TracksDirOutlineView<Node>: View where Node: Hashable, Node: Identifiable, Node: CustomStringConvertible{
let node: Node
let childKeyPath: KeyPath<Node, [Node]?>
@State var isExpanded: Bool = true

var body: some View {
    GeometryReader { geometry in
        if node[keyPath: childKeyPath] != nil {
            DisclosureGroup(
                isExpanded: $isExpanded,
                content: {
                    if isExpanded {
                        ForEach(node[keyPath: childKeyPath]!) { childNode in
                            TracksDirOutlineView(node: childNode, childKeyPath: childKeyPath, isExpanded: isExpanded)
                        }
                    }
                },
                label: { Text(node.description) })
        } else {
            Text(node.description)
        }
    }
}

}

The model / view model:

    struct FileItem: Hashable, Identifiable, CustomStringConvertible {
    var id: Self { self }
    var name: String
    var children: [FileItem]? = nil
    var description: String {
        switch children {
        case nil:
            return "📄 \(name)"
        case .some(let children):
            return children.isEmpty ? "📂 \(name)" : "📁 \(name)"
        }
    }
}

let data =
FileItem(name: "root", children:
            [FileItem(name: "child-1", children:
                        [FileItem(name: "child-1-1", children:
                                    [FileItem(name: "child-1-1-1"),
                                     FileItem(name: "child-1-1-2")]),
                         FileItem(name: "child-1-2", children:
                                    [FileItem(name: "child-1-2-1")]),
                         FileItem(name: "child-1-3", children: [])
                        ]),
             FileItem(name: "child-2", children:
                        [FileItem(name: "child-2-1", children: [])
                        ])
            ])

The call in ContentView:

TracksDirOutlineView(node: data, childKeyPath: \.children)

The result: enter image description here

Somehow the view is cut off, even if there is still plenty of space in the view. I am not sure, if and where there should be a frame modifier. If I remove the GeometryReader, then the view becomes very large and everything is pushed to the bottom and partly off-screen

enter image description here

Also, is this the right approach? Should I use NSRepesentable with the NSOutlineView?


Solution

  • It looks like this could be fixed by just replacing the Geometry Reader with VStack and adding a Spacer() at the bottom, e.g:

            VStack {
                if node[keyPath: childKeyPath] != nil {
                    DisclosureGroup(
                        isExpanded: $isExpanded,
                        content: {
                            if isExpanded {
                                ForEach(node[keyPath: childKeyPath]!) { childNode in
                                    TracksDirOutlineView(node: childNode, childKeyPath: childKeyPath, isExpanded: isExpanded)
                                }
                            }
                        },
                        label: { Text(node.description) })
                } else {
                    Text(node.description)
                }
                Spacer()
            }
    

    enter image description here