问题描述
这是SwiftUI中层次结构List
的简单演示。我正在macOS Big Sur上对其进行测试,但与其他UI工具包中的类似树组件不同,它会立即要求其所有子代。因此,我不能将其用于文件系统浏览器之类的东西。
是否有一种使其变得懒惰的方法,以便仅在扩展UI元素时要求children
?
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)
}
}
}
您可以在控制台中看到它要求所有内容-一直到树下。这是大树的性能问题。
...
Computing children property,name=3.4.4.1.4,depth=4
Computing children property,name=3.4.4.1.5,name=3.4.4.2,depth=3
Computing children property,name=3.4.4.2.1,name=3.4.4.2.2,depth=4
...
解决方法
我相信这可能是 SwiftUI 中的一个错误,我希望 Apple 能够解决这个问题。同时,您可以使用以下解决方法:
struct Node {
var id: String
var value: String
var children: [Node]?
}
struct LazyDisclosureGroup: View {
let node: Node
@State var isExpanded: Bool = false
var body: some View {
if node.children != nil {
DisclosureGroup(
isExpanded: $isExpanded,content: {
if isExpanded {
ForEach(node.children!,id: \.self.id) { childNode in
LazyDisclosureGroup(node: childNode)
}
}
},label: { Text(node.value) })
} else {
Text(node.value)
}
}
}
struct ContentView: View {
let node = Node(
id: "a",value: "a",children: [Node(id: "b",value: "b",children: nil),Node(id: "c",value: "c",children: nil)])
var body: some View {
List {
LazyDisclosureGroup(node: node)
}
}
}
我不知道这是否是最佳实践,但解决方法使用了 DisclosureGroup
在 List
内时“注意到”的观察结果。两者的结合产生相同的视觉结构和行为。