SwiftUI如何实现点击清空按钮重置List中所有Toggle为未选中状态
界面效果参考:
问题根因
你的清空逻辑不生效是两个核心问题导致的:
ListSection、RowItem都是结构体(值类型),你在按钮回调里写for var item in section.items拿到的是item的临时拷贝,修改拷贝不会影响原数组里的真实数据ExpandableList里用@State声明了本地items变量,相当于把父视图传进来的数据复制了一份存在子视图本地,你在父视图/模型层改数据完全同步不到子视图的本地状态上,Toggle自然不会重置
修改步骤
1. 调整数据模型定义
把ListSection里的items属性从常量改成变量,支持后续修改:
struct ListSection: Identifiable, Equatable { static func == (lhs: ListSection, rhs: ListSection) -> Bool { lhs.id == rhs.id } let id = UUID() let title: String var items: [RowItem] // 原let声明改为var let visible: Int }
2. 在HomeModel中新增清空方法
直接在视图模型层修改@Published标记的数据源,改动会自动触发视图刷新,不要在视图层遍历值拷贝:
class HomeModel: ObservableObject { // 原有items1、items2、sections、init代码保留不变 // ... // 新增全局清空方法 func clearAllSelections() { for sectionIndex in sections.indices { for itemIndex in sections[sectionIndex].items.indices { sections[sectionIndex].items[itemIndex].isSelected = false } } } }
3. 修改ExpandableList的状态定义
把本地@State的items改成@Binding,直接绑定上层的真实数据源,不存本地拷贝:
struct ExpandableList: View { private var title: String private var visibleElements: Int private var showAllButtonVisible: Bool @Binding var items: [RowItem] // 原@State声明改为@Binding @State private var isExpanded = false init(title: String, items: Binding<[RowItem]>, visible: Int) { // 入参改为Binding类型 self.title = title.lowercased() self._items = items // 绑定赋值 self.visibleElements = visible self.showAllButtonVisible = visible < items.wrappedValue.count } // 原有body内的交互逻辑不需要改动,直接保留 var body: some View { List { ForEach(isExpanded ? 0..<items.count : 0..<Array(items[0..<visibleElements]).count, id: \.self) { index in HStack { Text(items[index].rowName).fontWeight(.light) Spacer() Text("\(items[index].amount)").foregroundColor(.gray).fontWeight(.light) Toggle(isOn: Binding( get: { return items[index].isSelected }, set: { items[index].isSelected = $0 } )){}.toggleStyle(.checklist) }.listRowSeparator(.hidden) } if showAllButtonVisible { HStack { Button { withAnimation { isExpanded.toggle() } } label: { if isExpanded { Label("Show less \(title)", systemImage: "chevron.up").foregroundColor(Color(red: 55/255, green: 126/255, blue: 83/255, opacity: 1)).labelStyle(ImageToRightSideLabelStyle()) } else { Label("Show all \(title)", systemImage: "chevron.down").foregroundColor(Color(red: 55/255, green: 126/255, blue: 83/255, opacity: 1)).labelStyle(ImageToRightSideLabelStyle()) } } } } } } }
4. 调整MyForm里的传参和清空按钮逻辑
调用ExpandableList的时候传入绑定值,清空按钮直接调用模型的清空方法:
struct MyForm: View { @ObservedObject var model = HomeModel() var body: some View { NavigationView { VStack { Form { ForEach($model.sections, id: \.id) { $section in // 遍历带绑定的sections数组 Section { // 传入$section.items绑定值 ExpandableList(title: section.title, items: $section.items, visible: section.visible) } header: { Text(section.title).font(.title3).foregroundColor(.black).textCase(nil) } footer: { if section != model.sections.last { VStack { Divider().padding(.top, 15) } } } } }.navigationTitle(Text("Filter stocks")) HStack { Button { // 直接调用模型层的清空方法 model.clearAllSelections() } label: { Text("Clear filters").foregroundColor(.black) }.padding(.leading, 30) Spacer() Button(action: { print("sign up bin tapped") }) { Text("Show 1506 assets") .font(.system(size: 18)) .padding() .foregroundColor(.black) } .background(Color.green) .cornerRadius(10) .padding(.trailing, 30) } } } } }
效果说明
改完之后:
- 单个Toggle切换会直接修改HomeModel里的真实数据,不会出现子视图状态和上层不同步的问题
- 点击清空按钮会直接修改模型层的所有选中状态,所有绑定了数据源的Toggle会自动刷新为未选中状态
- 原有展开/收起列表的逻辑不受影响
内容的提问来源于stack exchange,提问作者Artiom
相关产品推荐
相关产品推荐


