'How to change toggle on just one Core Data item using ForEach in SwiftUI?

How to change just one toggle in a list without subviews? I know how to make it work if I extract Subview from everything inside ForEach, but how to do it on one view?

I cannot use subview, because I have a problem later if I want to delete an item from this subview. It gives me some errors I don't know how to fix, so I am trying to make it on one view where I don't have this error.

The code for the list is quite simple:

import SwiftUI

struct ContentView: View {
    
    @Environment(\.managedObjectContext) var moc
    var fetchRequest: FetchRequest<Item>
    var items: FetchedResults<Item> { fetchRequest.wrappedValue }
    
    @State private var doneStatus : Bool = false
    
    var body: some View {
        
        NavigationView {
            List {
                ForEach(items, id: \.self) {item in
                    HStack {
                        Text("\(item.name ?? "default item name")")
                        
                        Spacer()
                        
                        Toggle(isOn: self.$doneStatus) {
                            Text("Done")
                        }
                        .labelsHidden()
                        .onAppear {
                            self.doneStatus = item.done
                        }
                        .onTapGesture {
                            self.doneStatus.toggle()
                            item.done.toggle()
                            try? self.moc.save()
                        }
                    }
                }
                .onDelete(perform: removeItem)
            }
            .navigationBarTitle("Items")
            .navigationBarItems(
                leading:
                Button(action: {
                    for number in 1...3 {
                        let item = Item(context: self.moc)
                        item.date = Date()
                        item.name = "Item \(number)"
                        item.done = false
                        
                        do {
                            try self.moc.save()
                        }catch{
                            print(error)
                        }
                    }
                }) {
                    Text("Add 3 items")
                }
            )
        }
    }
    
    init() {
        fetchRequest = FetchRequest<Item>(entity: Item.entity(), sortDescriptors: [
            NSSortDescriptor(keyPath: \Item.name, ascending: true)
        ])
    }
    
    func removeItem(at offsets: IndexSet) {
        for offset in offsets {
            let item = items[offset]
            moc.delete(item)
        }
        try? moc.save()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        //Test data
        let testItem = Item.init(context: context)
        testItem.date = Date()
        testItem.name = "Item name"
        testItem.done = false
        return ContentView().environment(\.managedObjectContext, context)
    }
}

I am using 1 Core Data Entity: Item. With 3 attributes: date (Date), done (Boolean), name (String).

PROBLEM

When I tap on one toggle, all other toggles change as well.

I couldn't find a solution working with Core Data. I guess maybe I should use .id instead of .self? And add another attribute to my entity: id (UUID). But I tried to do it and failed.

I will appreciate any kind of help.



Solution 1:[1]

You bound all Toggle to one state... so

  1. remove this
//    @State private var doneStatus : Bool = false
  1. bind Toggle dynamically to currently iterating item (note: .onAppear/.onTapGesture not needed anymore)
Toggle(isOn: Binding<Bool>(
    get: { item.done },
    set: {
        item.done = $0
        try? self.moc.save()
    })) {
    Text()
}
.labelsHidden()

backup

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1