Home > Software design >  SwiftUI clear button for all Toggles in a List
SwiftUI clear button for all Toggles in a List

Time:06-15

I am new to SwiftUI and here is what I want to achieve:

I have a Form with sections. Each section has a list and each row has a Toggle I can select/deselect. Below the list, I have a clear button. With a click, I would like to reset all the Toggles to a deselected state.

enter image description here

Model (each ListSection represents Section in Form):

struct RowItem: Identifiable {
    let id = UUID()
    var isSelected: Bool
    var rowName: String
    var amount: Int
}

struct ListSection: Identifiable, Equatable {
    static func == (lhs: ListSection, rhs: ListSection) -> Bool {
        lhs.id == rhs.id
    }
    
    let id = UUID()
    let title: String
    let items: [RowItem]
    let visible: Int
}

class HomeModel: ObservableObject {
    private let items1 = [RowItem(isSelected: false, rowName: "Energy", amount: 24),
                          RowItem(isSelected: true, rowName: "Automotive", amount: 70),
                          RowItem(isSelected: true, rowName: "Materials", amount: 56),
                          RowItem(isSelected: false, rowName: "Industrials", amount: 109),
                          RowItem(isSelected: true, rowName: "Consumer1", amount: 209),
                          RowItem(isSelected: true, rowName: "Consumer2", amount: 12),]

    private let items2 = [RowItem(isSelected: true, rowName: "Europe", amount: 302),
                          RowItem(isSelected: true, rowName: "America", amount: 589),
                          RowItem(isSelected: false, rowName: "Asia", amount: 67),
                          RowItem(isSelected: false, rowName: "Africa", amount: 207),
                          RowItem(isSelected: true, rowName: "Oceania", amount: 9)]

    @Published var sections: [ListSection]

    init() {
        self.sections = [ListSection(title: "Sectors", items: items1, visible: 4),
                         ListSection(title: "Regions", items: items2, visible: 5)]
    }
}

struct ExpandableList: View {
    private var title: String
    private var visibleElements: Int
    private var showAllButtonVisible: Bool
    
    @State private var items: [RowItem]
    @State private var isExpanded = false
    
    init(title: String, items: [RowItem], visible: Int) {
        self.title = title.lowercased()
        self.items = items
        self.visibleElements = visible
        self.showAllButtonVisible = visible < items.count
    }
    
    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())
                        }
                    }
                }
            }
        }
    }
}

struct MyForm: View {
    @ObservedObject var model = HomeModel()
    
    var body: some View {
        NavigationView {
            VStack {
                Form {
                    ForEach(model.sections, id: \.id) { section in
                        Section {
                            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 {
                        for section in model.sections {
                            for var item in section.items {
                                item.isSelected = false
                                print("Tag1 false")
                            }
                        }
                    } 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) // If you have this
                    .cornerRadius(10)
                    .padding(.trailing, 30)
                }
            }

        }
    }
}

I assume I need to use Binding somewhere, but not sure where.

CodePudding user response:

I made a small project to address your issue. The main takeaway is that you need to let SwiftUI know when to redraw the view after the model or model element's properties change. That can be done with @Published and/or objectWillChange which is available to any class conforming to the ObservableObject protocol.

import SwiftUI

struct ContentView: View {
    @StateObject var model = Model()
    
    class Model: ObservableObject {
        @Published var items = [
            Item(zone: "America", count: 589),
            Item(zone: "Asia", count: 67),
            Item(zone: "Africa", count: 207),
            Item(zone: "Oceania", count: 9)
        ]
        
        class Item: ObservableObject, Hashable {
            static func == (lhs: ContentView.Model.Item, rhs: ContentView.Model.Item) -> Bool {
                lhs.id == rhs.id
            }
            
            let id = UUID()
            let zone: String
            let count: Int
            
            @Published var selected: Bool = false
            
            init(zone: String, count: Int) {
                self.zone = zone
                self.count = count
            }
            
            func toggle() {
                selected.toggle()
            }
            
            func hash(into hasher: inout Hasher) {
                hasher.combine(id)
            }
        }
    }
    
    var body: some View {
        VStack {
            ForEach(Array(model.items.enumerated()), id: \.element) { index, item in
                HStack {
                    Text(item.zone)
                    Spacer()
                    Text(String(item.count))
                    Button {
                        item.toggle()
                        print(index, item.selected)
                    } label: {
                        Image(systemName: item.selected ? "checkmark.square" : "square")
                            .resizable()
                            .frame(width: 24, height: 24)
                            .font(.system(size: 20, weight: .regular, design: .default))
                    }
                }
                .padding(10)
                .onReceive(item.$selected) { isOn in
                    model.objectWillChange.send()
                }
            }
            
            Button {
                model.items.forEach { item in
                    item.selected = false
                }
                model.objectWillChange.send()
            } label: {
                Text("Clear")
            }

        }
    }
}

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

https://github.com/aibo-cora/SwiftUI/blob/main/ResetCheckBoxes.md

  • Related