I am displaying a checklist that allows the user to check options on and off:
ForEach(checklist.items.sorted(by: { $0.sortOrder < $1.sortOrder })) { item in
@Bindable var item = item
Toggle(item.title, isOn: $item.isChecked)
.toggleStyle(CheckboxToggleStyle())
.onChange(of: item.isChecked) { newValue in
checklistChanged()
}
}
func checklistChanged() {
//check some things and update other parts of the UI
}
This works fine. However, the checklist is a SwiftData @Model which has a method to reset the checklist:
func reset() {
for item in self.items {
item.isChecked = false
}
}
And calling this function causes the checklistChanged()
function in my view to get called (multiple times) because the isChecked
property is being changed on all the items. I only want the checklistChanged()
func to be called specifically when a user taps and changes a checkbox value. How can I separate these concerns? I tried using onTapGesture
instead of onChange
on the toggle but that doesn't get called.
There is no way to tell the difference between the Toggle
changing isChecked
, vs your code changing isChecked
. You need some extra information to indicate who changed it.
The simplest way is to just add a @Transient
property to your model, to indicate whether isChecked
has been programmatically changed.
@Transient
var programmaticallyChanged: Bool = false
If you need to scale this up for all the properties in your model, you can write a member macro that is attached to your @Model
class to generate these.
You can set this in reset
:
func reset() {
for item in self.items {
if item.isChecked {
item.isChecked = false
item.programmaticallyChanged = true
}
}
}
Then you can check for this flag in onChange
:
.onChange(of: item.isChecked) {
if item.programmaticallyChanged {
item.programmaticallyChanged = false
} else {
checklistChanged()
}
}