In SwiftUI handling the state of an app first seemed to be quite simple. There are essentially two property wrappers: @EnvironmentObject and @ObservedObject (and @State). But it turned out that it isn't that simple at all.
I posted several questions here. Most of them have been answered very well: SwiftUI onDelete List with Toggle, SwiftUI ForEach with .indices() does not update after onDelete, SwiftUI: Index out of range when deleting cells with toggle
But every time I go a step further, some principal problems stay. These are
NavigationLink in a List or a ForEach to a SubView and reflecting changes back to the MainView. (I tried several solutions like working with .indices on the array and make a bindable on the fly (SwiftUI onDelete List with Toggle)).
To reflect changes back I also tried to work with .onReceive() which also didn't work.someArray.firstIndex(where {condition})!. You can find this in the Apple tutorials for SwiftUI. I also run into problems, when I use indexes in a ForEach-closure and then try to swipe-delete with .onDelete(). The error message is:Thread 1: Fatal error: Index out of range
@EnvironmentObject-model that are two or more layers behind the mainView? Working with the formula in 2. produces very long variable-names. This doesn't seem to be right.Here is one example, which nearly works. The only thing that does not work is updating the MainView when I change values in the SubViews. But it doesn't feel right for me to use several ObservedObjects in my app.
class Model: ObservableObject {
@Published var items: [Item]
init(name: String, items: [Item]) {
self.items = items
}
}
class Item: Identifiable, ObservableObject {
var id = UUID()
var itemName: String
@Published var subItems: [SubItem]
init(itemName: String, payments: [SubItem]) {
self.itemName = itemName
self.subItems = payments
}
}
class SubItem: Identifiable, ObservableObject {
var id = UUID()
var isOn: Bool
init(isOn: Bool) {
self.isOn = isOn
}
}
struct MainView: View {
@EnvironmentObject var model: Model
var body: some View {
NavigationView {
List {
ForEach(model.items) {item in
NavigationLink(destination: SubView1(item: item)) {
HStack{
Text(item.itemName)
Text(item.subItems[0].isOn ? "True":"False") // I know: bad, but only for quick debugging reasons
Text(item.subItems[1].isOn ? "True":"False")
}
}.onReceive(item.objectWillChange, perform: {self.model.objectWillChange.send()})
}.onDelete(perform: delete)
}
}
}
func delete(at offsets: IndexSet) {
self.model.items.remove(atOffsets: offsets)
}
}
struct SubView1: View {
@ObservedObject var item: Item
var body: some View {
List {
ForEach(item.subItems) {subItem in
NavigationLink(destination: SubView2(subItem: subItem)) {
ToggleView(subItem: subItem)
}
}.onDelete(perform: delete)
}
}
func delete(at offsets: IndexSet) {
self.item.subItems.remove(atOffsets: offsets)
}
}
struct SubView2: View {
@ObservedObject var subItem: SubItem
var body: some View {
Toggle(isOn: $subItem.isOn) {
Text("Toggle-Text")
}
}
}
struct ToggleView: View {
@ObservedObject var subItem: SubItem
var body: some View {
Toggle(isOn: $subItem.isOn) {
Text("Toggle-Text")
}
}
}
What you can do is to access the binding of an item and use it in the ToggleView:
struct SubView1: View {
@ObservedObject var item: Item
var body: some View {
List {
ForEach(0..<item.subItems.count, id: \.self) { index in
NavigationLink(destination: SubView2(subItem: self.$item.subItems[index])) { // <- pass the binding
ToggleView(subItem: self.$item.subItems[index]) // <- pass the binding
}
}.onDelete(perform: delete)
}
}
...
}
struct SubView2: View {
@Binding var subItem: SubItem // <- receive the binding
var body: some View {
Toggle(isOn: $subItem.isOn) {
Text("Toggle-Text")
}
}
}
struct ToggleView: View {
@Binding var subItem: SubItem // <- receive the binding
var body: some View {
Toggle(isOn: $subItem.isOn) {
Text("Toggle-Text")
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With