For some reason I don't understand, when I add/remove items from a @State var in MainView, the OutterViews are not being updated properly.
What I am trying to achieve is that the user can only "flag" (select) one item at a time. For instance, when I click on "item #1" it will be flagged. If I click on another item then "item #1" will not be flagged anymore but only the new item I just clicked.
Currently, my code shows all items as if they were flagged even when they are not anymore. The following code has the minimum structure and functionality I'm implementing for MainView, OutterView, and InnerView.
I've tried using State vars instead of the computed property in OutterView, but it doesn't work. Also, I tried using a var instead of the computed property in OutterViewand initialized it in init() but also doesn't work.
Hope you can help me to find what I am doing wrong. Thanks!
struct MainView: View {
@State var flagged: [String] = []
var data: [String] = ["item #1", "item #2", "item #3", "item #4", "item #5"]
var body: some View {
VStack(spacing: 50) {
VStack {
ForEach(data, id:\.self) { text in
OutterView(text: text, flag: flagged.contains(text)) { (flag: Bool) in
if flag {
flagged = [text]
} else {
if let index = flagged.firstIndex(of: text) {
flagged.remove(at: index)
}
}
}
}
}
Text("Flagged: \(flagged.description)")
Button(action: {
flagged = []
}, label: {
Text("Reset flagged")
})
}
}
}
struct OutterView: View {
@State private var flag: Bool
private let text: String
private var color: Color { flag ? Color.green : Color.gray }
private var update: (Bool)->Void
var body: some View {
InnerView(color: color, text: text)
.onTapGesture {
flag.toggle()
update(flag)
}
}
init(text: String, flag: Bool = false, update: @escaping (Bool)->Void) {
self.text = text
self.update = update
_flag = State(initialValue: flag)
}
}
struct InnerView: View {
let color: Color
let text: String
var body: some View {
Text(text)
.padding()
.background(
Capsule()
.fill(color))
}
}

Here's a simple version that does what you're looking for (explained below):
What's happening:
Itemthat has an ID for each item, the flagged state of that item, and the titleStateManagerkeeps an array of those items. It also has a custom binding for each index of the array. For thegetter, it just returns the state of the model at that index. For thesetter, it makes a new copy of the item array. Any time a checkbox is set, it unchecks all of the other boxes.ForEachnow gets an enumeration of theitems. This could be done without enumeration, but it was easy to write the custom binding by index like this. You could also filter by ID instead of index. Note that because of the enumeration, it's using.1.idfor the id parameter --.1is the item while.0is theindex.ForEach, the custom binding from before is created and passed to the subviewBindingis passed to)Using this strategy of an
ObservableObjectthat contains all of your state and passes it on via @Published properties and @Bindings makes organizing your data a lot easier. It also avoids having to pass closures back and forth like you were doing initially with yourupdatefunction. This ends up being a pretty idiomatic way of doing things in SwiftUI.