You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI 如何编程实现层级List的全部展开/折叠功能

SwiftUI 树形List全局展开/折叠实现方案

不需要自定义递归视图,原生List+OutlineGroup原生能力即可实现,核心是接管系统的展开状态管理。

核心实现思路

  • 系统简写的List(_:children:content:)树形写法会自动管理节点展开状态,没有暴露全局控制的绑定接口,因此需要显式使用支持展开状态绑定的OutlineGroup组件嵌入List
  • 用Set<UUID>类型的状态存储所有当前处于展开状态的节点ID:节点ID在集合内则为展开状态,不在则为折叠状态
  • 写递归方法遍历整棵树,拿到所有带子节点的可展开节点ID集合:点击「全部展开」时把所有可展开ID写入状态集合,点击「全部折叠」时直接清空状态集合即可

完整可运行代码

import SwiftUI

struct ContentView: View {
    // 树形数据源
    @State private var tree: [MenuItem] = sampleMenuItems
    // 存储所有展开状态的节点ID
    @State private var expandedNodeIDs: Set<UUID> = []
    
    init() {
        // 若需要默认全展开,解开下面注释即可
        // _expandedNodeIDs = State(initialValue: getAllExpandableIDs(from: sampleMenuItems))
    }
    
    var body: some View {
        NavigationView {
            List {
                // 显式使用OutlineGroup,绑定展开状态集合
                OutlineGroup(tree, id: \.id, children: \.subMenuItems, isExpanded: $expandedNodeIDs) { node in
                    Text(node.name)
                }
            }
            .navigationTitle("设备菜单")
            .toolbar {
                ToolbarItemGroup(placement: .navigationBarTrailing) {
                    Button("全部展开") {
                        expandedNodeIDs = getAllExpandableIDs(from: tree)
                    }
                    Button("全部折叠") {
                        expandedNodeIDs.removeAll()
                    }
                }
            }
        }
    }
    
    // 递归获取所有可展开(带子节点)的节点ID
    private func getAllExpandableIDs(from nodes: [MenuItem]) -> Set<UUID> {
        var ids = Set<UUID>()
        for node in nodes {
            guard let subNodes = node.subMenuItems, !subNodes.isEmpty else { continue }
            ids.insert(node.id)
            // 递归加入子节点的可展开ID
            ids.formUnion(getAllExpandableIDs(from: subNodes))
        }
        return ids
    }
}

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

// 原有模型保持不变
struct MenuItem: Identifiable {
    var id = UUID()
    var name: String
    var subMenuItems: [MenuItem]?
}

// 示例数据
var espressoMachines = MenuItem(name: "Espresso Machines", subMenuItems: espressoMachineMenuItems)
var grinders = MenuItem(name: "Grinders", subMenuItems: grinderMenuItems)
var otherEquipment = MenuItem(name: "Other Equipment", subMenuItems: otherMenuItems)

let sampleMenuItems = [ espressoMachines, grinders, otherEquipment]
                        
// Espresso Machines 子菜单
let espressoMachineMenuItems = [ MenuItem(name: "Leva", subMenuItems: [ MenuItem(name: "Leva X"), MenuItem(name: "Leva S") ]),
                                 MenuItem(name: "Strada", subMenuItems: [ MenuItem(name: "Strada EP" ), MenuItem(name: "Strada AV"), MenuItem(name: "Strada MP"), MenuItem(name: "Strada EE") ]),
                                 MenuItem(name: "KB90"),
                                 MenuItem(name: "Linea", subMenuItems: [ MenuItem(name: "Linea PB X"), MenuItem(name: "Linea PB"), MenuItem(name: "Linea Classic") ]),
                                 MenuItem(name: "GB5"),
                                 MenuItem(name: "Home", subMenuItems: [ MenuItem(name: "GS3"), MenuItem(name: "Linea Mini") ])
                                ]

// Grinders 子菜单
let grinderMenuItems = [ MenuItem(name: "Swift"),
                         MenuItem(name: "Vulcano"),
                         MenuItem(name: "Swift Mini"),
                         MenuItem(name: "Lux D")
                        ]

// 其他设备子菜单
let otherMenuItems = [ MenuItem(name: "Espresso AV"),
                         MenuItem(name: "Espresso EP"),
                         MenuItem(name: "Pour Over"),
                         MenuItem(name: "Steam")
                        ]

补充说明

  • 不需要自己实现DisclosureGroup的递归逻辑,OutlineGroup已经封装了完整的层级渲染和折叠交互,只接管状态绑定即可,性能和原生简写写法完全一致
  • 如果需要记忆用户手动展开/折叠的状态,只需要把expandedNodeIDs做持久化存储即可,不影响全局展开/折叠功能
  • 低版本系统兼容只需要对应调整导航栏相关API,核心展开控制逻辑不受影响

内容的提问来源于stack exchange,提问作者David

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 10:48:19