SwiftUI 中默认展开的 List 或 OutlineGroup

List or OutlineGroup expanded by default in SwiftUI

SwiftUI 中的List 或OutlineGroup 如何在创建视图时使其部分(或全部)分支默认展开。这似乎可以通过具有绑定的 DisclosureGroup 实现。

这对于恢复状态或自定义演示视图很有用。

我也在搜索这个,我相信 OutlineGroup 不支持这个。相反,我已经转移到 DisclosureGroup,OutlineGroup 将其用于其实现,并直接支持扩展布尔绑定以及允许嵌套:

struct ToggleStates {
    var oneIsOn: Bool = false
    var twoIsOn: Bool = true
}
@State private var toggleStates = ToggleStates()
@State private var topExpanded: Bool = true

var body: some View {
    DisclosureGroup("Items", isExpanded: $topExpanded) {
        Toggle("Toggle 1", isOn: $toggleStates.oneIsOn)
        Toggle("Toggle 2", isOn: $toggleStates.twoIsOn)
        DisclosureGroup("Sub-items") {
            Text("Sub-item 1")
        }
    }
}

示例来自 https://developer.apple.com/documentation/swiftui/disclosuregroup

OutlineGroup 的可重用版本,其中可扩展性受到控制。

import SwiftUI

struct NodeOutlineGroup<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 {
        if node[keyPath: childKeyPath] != nil {
            DisclosureGroup(
                isExpanded: $isExpanded,
                content: {
                    if isExpanded {
                        ForEach(node[keyPath: childKeyPath]!) { childNode in
                            NodeOutlineGroup(node: childNode, childKeyPath: childKeyPath, isExpanded: isExpanded)
                        }
                    }
                },
                label: { Text(node.description) })
        } else {
            Text(node.description)
        }
    }
}

struct ContentView: View {
    var body: some View {
        List {
            NodeOutlineGroup(node: data, childKeyPath: \.children, isExpanded: true)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

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: "users", children:
    [FileItem(name: "user1234", children:
      [FileItem(name: "Photos", children:
        [FileItem(name: "photo001.jpg"),
         FileItem(name: "photo002.jpg")]),
       FileItem(name: "Movies", children:
         [FileItem(name: "movie001.mp4")]),
          FileItem(name: "Documents", children: [])
      ]),
     FileItem(name: "newuser", children:
       [FileItem(name: "Documents", children: [])
       ])
    ])

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

非常好,Paul B.,谢谢。 但我想对显示的行有更多的控制。所以我稍微扩展了你的解决方案:

struct NodeOutlineGroup<Node, Content>: View where Node: Hashable, Node: Identifiable, Node: CustomStringConvertible, Content: View {
    let node: Node
    let childKeyPath: KeyPath<Node, [Node]?>
    @State var isExpanded: Bool = true
    let content: (Node) -> Content
    
    var body: some View {
        if node[keyPath: childKeyPath] != nil {
            DisclosureGroup(
                isExpanded: $isExpanded,
                content: {
                    if isExpanded {
                        ForEach(node[keyPath: childKeyPath]!) { childNode in
                            NodeOutlineGroup(node: childNode, childKeyPath: childKeyPath, isExpanded: isExpanded, content: content)
                        }
                    }
                },
                label: { content(node) })
        } else {
            content(node)
        }
    }
}

使用情况:

struct ContentView: View {
    var body: some View {
        List {
            NodeOutlineGroup(node: data, childKeyPath: \.children, isExpanded: true) { node in
                Text(node.description)
            }
        }
    }
}