HStack{
ForEach($rs.newsCategoryCollection){ $category in
Toggle(category.id, isOn: $category.isSelected)
.toggleStyle(.button)
.onChange(of: category.isSelected) {value in
print("Changed")
if value{
rs.unselectOtherCategory(id: category.id)
print("Selected: \(category.id)")
viewModel.loadNews()
}else{
category.isSelected = true
print("Deselected: \(category.id)")
}
}
}
}
Here I am trying to have toggle button in horizontal view. I Want this to be used as selectors to select only one category at a time.
So after user selects any one of the category the other category should be deselected using this method: rs.unselectOtherCategory(id: category.id)
So I am using onChange to detect user selecting a category by tracking the change of value. But using the previous method to deselect all other categories changes the value of a previously selected category and called the onChange again.
The problem gets more complecated when I use the else block to make sure If the user tap on a selected button and deselect which makes all the button deselected, the else block can counter it and select the only button selected.
How can I avoid this repeated call of onChange.
I want to the user to be able to tap on a button and select it, other buttons will be deselected. But If an user taps on a button that is already selected the button will be remain selected.
This looks more like a Picker
than a group of Toggle
s. Don't use Toggle
s when you want one thing to be selected at a time.
You can change the Toggle
s to Button
s:
HStack{
ForEach($rs.newsCategoryCollection){ $category in
Button(category.id) {
if !category.isSelected {
category.isSelected = true
rs.unselectOtherCategory(id: category.id)
viewModel.loadNews()
}
}
.padding(8)
.background(.accent.opacity(category.isSelected ? 0.1 : 0), in: RoundedRectangle(cornerRadius: 10))
}
}
That said, I would suggest using Picker
, with .segmented
picker style, which looks similar to a row of buttons.
struct ContentView: View {
@State var rs = ...
@State var selectedCategory: NewsCategory.ID = "" // assuming NewsCategory.ID is a String
var body: some View {
Picker(selection: $selectedCategory) {
ForEach(rs.newsCategoryCollection){ category in
Text(category.id)
}
} label: {
}
.pickerStyle(.segmented)
.onChange(of: selectedCategory) {
viewModel.loadNews()
}
}
}
Now you don't even need an isSelected
property in NewsCategory
. If you really need it for some reason, you can add a selectCategory
method to rs
, e.g.
// selectCategory could be implemented as:
mutating func selectCategory(id: String) {
for i in newsCategoryCollection.indices {
if newsCategoryCollection[i].id != id {
newsCategoryCollection[i].isSelected = false
} else {
newsCategoryCollection[i].isSelected = true
}
}
}
Then call this in onChange
.