Here's a simple demo of the hierarchical List
in SwiftUI. I'm testing it on macOS Big Sur, but unlike similar tree components in other UI toolkits, it asks for all its children immediately. So I can't use it for something like a file system browser.
Is there a way to make it lazy, so that it only asks for children
when the UI element is expanded?
class Thing: Identifiable {
let id: UUID
let depth: Int
let name: String
init(_ name: String, depth: Int = 0) {
self.id = UUID()
self.name = name
self.depth = depth
}
/// Lazy computed property
var children: [Thing]? {
if depth >= 5 { return nil }
if _children == nil {
print("Computing children property, name=\(name), depth=\(depth)")
_children = (1...5).map { n in
Thing("\(name).\(n)", depth:depth+1)
}
}
return _children
}
private var _children: [Thing]? = nil
}
struct ContentView: View {
var things: [Thing] = [Thing("1"), Thing("2"), Thing("3")]
var body: some View {
List(things, children: \.children) { thing in
Text(thing.name)
}
}
}
Even though the initial UI only displays the top nodes:
You can see in the console that it asks for everything - all the way down the tree. This is a performance problem for large trees.
...
Computing children property, name=3.4.4.1.4, depth=4
Computing children property, name=3.4.4.1.5, depth=4
Computing children property, name=3.4.4.2, depth=3
Computing children property, name=3.4.4.2.1, depth=4
Computing children property, name=3.4.4.2.2, depth=4
...
I believe this could be a bug in SwiftUI and I hope Apple will fix this. In the meantime, you can use the following workaround:
I don't know if that's a best practice but the workaround uses the observation that
DisclosureGroup
"notices" when it is inside aList
. The combination of the two produces the same visual structure and behaviour.