SwiftUI使用DisclosureGroup实现可展开列表时设置isExpanded=false无法自动收起问题求助
问题原因
- 状态隔离失效:你将
isExpanded声明为SingleSelectionRow内部的@State属性,每个行实例的该变量完全独立,外部(包括其他行、父视图)都无法直接访问修改。你在无children的行点击时设置的isExpanded = false,仅修改了当前无children行的独立状态,完全无法触达正在展开的有children行的isExpanded值,这是收起逻辑不生效的核心原因。 - 点击逻辑冲突:你给已经绑定
$isExpanded的DisclosureGroup额外添加了.onTapGesture手动切换展开状态。而DisclosureGroup本身点击label时会自动修改绑定的展开属性,两套逻辑叠加会导致展开状态被重复切换,出现点击无响应的异常。 - 子项点击未触发收起:你在
DisclosureGroup的content区域的子项按钮action中仅调用了外部传入的self.action,没有主动将当前行的isExpanded设为false,因此点击当前展开列表的子选项时,列表不会自动收起。
修复思路
- 将
isExpanded的状态从行内部上移到父视图统一管理,实现跨行状态控制。父视图可记录当前展开的行唯一标识,当点击任意其他行/子项时直接清空展开标识,即可自动收起所有展开列表。 - 移除
DisclosureGroup上多余的.onTapGesture,依赖组件本身的绑定属性处理点击切换,避免逻辑冲突。 - 在
DisclosureGroup的content子项按钮的action中添加收起逻辑,点击子项时主动将展开状态设为false。
修正后代码示例
行组件修改
struct SingleSelectionRow: View { // 移除内部@State的isExpanded,替换为绑定属性,由父视图统一控制 @Binding var isExpanded: Bool @Binding var selectSingle: Int @Binding var selectedFilter: String? var title: String var isSelected: Bool var childrenList: [FilterChildren] @State var filterDetail: SortAndFilterData @State var singleSelectedFilters: [String] var action: () -> Void var body: some View { if selectSingle == 1 { // 无children的普通选项行 if childrenList.isEmpty { Button(action: { self.action() // 清空当前行的展开状态,父视图会同步收起其他展开列表 isExpanded = false }) { Text(self.title.uppercased()) .font(.custom("DM Sans",size:15)) .foregroundColor(isSelected ? Color(red: 10 / 255, green: 207 / 255, blue: 131 / 255) : Color.black) } } // 有children的可展开行 if !childrenList.isEmpty { DisclosureGroup( isExpanded: $isExpanded, content: { ForEach(childrenList, id: \.id) { item in Button(action: { self.action() // 点击子项后主动收起当前展开的列表 isExpanded = false }) { Text(item.name ?? "") .font(.custom("DM Sans",size:15)) .frame(maxWidth: .infinity, alignment: .leading) } } }, label: { Text(self.title.uppercased()) .foregroundColor(isSelected ? Color(red: 10 / 255, green: 207 / 255, blue: 131 / 255) : Color.black) } ) // 移除多余的.onTapGesture,使用DisclosureGroup原生点击逻辑 } } } }
父视图实现示例(统一管理展开状态)
struct FilterListView: View { // 父视图维护当前展开的行唯一标识,nil表示无展开项 @State private var expandedRowID: String? @Binding var selectedFilter: String? var body: some View { ForEach(filterRowList) { row in SingleSelectionRow( // 绑定当前行的展开状态:仅当当前行ID与expandedRowID一致时为展开 isExpanded: Binding( get: { expandedRowID == row.id }, set: { newValue in // 展开时记录当前行ID,收起时清空标识 expandedRowID = newValue ? row.id : nil } ), selectSingle: .constant(1), selectedFilter: $selectedFilter, title: row.title, isSelected: row.isSelected, childrenList: row.childrenList, filterDetail: row.filterDetail, singleSelectedFilters: row.singleSelectedFilters, action: { /* 原有选中回调逻辑 */ } ) } } }
内容的提问来源于stack exchange,提问作者Begbie
相关产品推荐
相关产品推荐

