I'm attempting to implement some swiftUI UI code to support filtering of a list.
One part of the filtering involves displaying one checkbox for each case/value of an enum (TangleType below)
TangleFilter is a model class that includes an array of TangleTypeFilter objects (each owning a single bool value and a binding)
Expected behaviour: when user taps a checkbox, the checkbox toggles the display and the filter model object toggles its value.
Actual behaviour: the model is updating appropriately, however the UI is not updating. (the single filter below the list does in fact behave correctly
any and all guidance greatly appreciated
Mike
Code Block struct ContentView: View { @State var isChecked: Bool = false @ObservedObject var filter = TangleFilter() @ObservedObject var singleFilter: TangleTypeFilter init() { self.singleFilter = TangleTypeFilter(tangleType: .grid) } var body: some View { VStack{ List(filter.tangleTypes, id: \.self) {tangleTypeFilter in HStack { // when uncommented the following line returns the following // compile error: // Use of unresolved identifier '$tangleTypeFilter' // CheckBox(isChecked: $tangleTypeFilter.isChecked) CheckBox(isChecked: tangleTypeFilter.binding) Text("checked? \(tangleTypeFilter.isChecked.description)") } } CheckBox(isChecked: $singleFilter.isChecked) } } } struct CheckBox: View { @Binding var isChecked: Bool { didSet { print("setting isChecked: \(isChecked)") } } var imageName: String { return isChecked ? "checkmark.square" : "square" } var body: some View { Button(action: { self.isChecked.toggle() }) { Image(systemName: self.imageName) } } } enum TangleType: String, Codable, CaseIterable { static let filterArray: [TangleTypeFilter] = { var result: [TangleTypeFilter] = [] for tangleType in TangleType.allCases { result.append(TangleTypeFilter(tangleType: tangleType)) } return result }() case grid case row } class TangleFilter: ObservableObject { @Published var tangleTypes: [TangleTypeFilter] = TangleType.filterArray } class TangleTypeFilter: ObservableObject { var tangleType: TangleType @Published var isChecked: Bool lazy var binding: Binding<Bool> = Binding(get: { return self.isChecked }, set: { self.isChecked = $0 }) init(tangleType: TangleType) { self.tangleType = tangleType self.isChecked = false } } extension TangleTypeFilter: Hashable { static func == (lhs: TangleTypeFilter, rhs: TangleTypeFilter) -> Bool { return lhs.tangleType == rhs.tangleType } func hash(into hasher: inout Hasher) { hasher.combine(tangleType) } }
Code Block class TangleFilter: ObservableObject { @Published var tangleTypes: [TangleTypeFilter] = TangleType.filterArray }
When TangleTypeFilter is a reference type, changing some property would not be considered as changing tangleTypes.
Whether or not the property is @Published.
Thus, updating isChecked would not update UI.
One way of achieving what you want is to make your TangleTypeFilter a struct:
Code Block struct ContentView: View { @State var isChecked: Bool = false @ObservedObject var filter = TangleFilter() @State var singleFilter: TangleTypeFilter = TangleTypeFilter(tangleType: .grid) var body: some View { VStack{ List(filter.tangleTypes.indices, id: \.self) {tangleTypeIndex in HStack { CheckBox(isChecked: $filter.tangleTypes[tangleTypeIndex].isChecked) Text("checked? \(filter.tangleTypes[tangleTypeIndex].isChecked.description)") } } CheckBox(isChecked: $singleFilter.isChecked) } } } struct CheckBox: View { @Binding var isChecked: Bool { didSet { print("setting isChecked: \(isChecked)") } } var imageName: String { return isChecked ? "checkmark.square" : "square" } var body: some View { Button(action: { self.isChecked.toggle() }) { Image(systemName: self.imageName) } } } enum TangleType: String, Codable, CaseIterable { static let filterArray: [TangleTypeFilter] = { var result: [TangleTypeFilter] = [] for tangleType in TangleType.allCases { result.append(TangleTypeFilter(tangleType: tangleType)) } return result }() case grid case row } class TangleFilter: ObservableObject { @Published var tangleTypes: [TangleTypeFilter] = TangleType.filterArray } struct TangleTypeFilter { var tangleType: TangleType var isChecked: Bool init(tangleType: TangleType) { self.tangleType = tangleType self.isChecked = false } }