0

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?

Money
  • 107
  • 5

1 Answers1

0

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

Ashley Mills
  • 50,474
  • 16
  • 129
  • 160
  • 1
    Yes, this works. But if you put this view A in another view B's VStack, all Views in the VStack below are moved off-screen. But one solution is to wrap view A with a frame modifier and restrict the height. This is on a Mac App (where virtually the screen size is not restricted). On an iPhone, the problem may even not occur. But as I mentioned with the help of Ashley it is working now. – Money Jan 13 '23 at 14:18