Search code examples
swiftuiswiftdata

Swiftdata not refreshing


I implemented swiftdata to my code and now the list is not refreshing after adding an item. Before implementing swiftdata it worked.

Thats the model:

@Model
class Einkauf {
    let name: String
    var anzahl: Int
    
    init(name: String, anzahl: Int) {
        self.name = name
        self.anzahl = anzahl
    }
}

Thats the ViewModel with the func:


class EinkaufViewModel: ObservableObject {
    
    //@Environment(\.modelContext) var context
    
    //Before SwiftData there was @Published and it worked.
    @Query var items: [Einkauf] = [
        Einkauf(name: "Apfel", anzahl: 1),
        Einkauf(name: "Fanta", anzahl: 10)
    ]
    
    func addItem(context: ModelContext) {
        let newItem = Einkauf(name: "Neuer Artikel", anzahl: 0)
        context.insert(newItem)
        //items.append(newItem)
    }
}

Thats the View:

struct EinkaufslisteDetailView: View {
    
    @Environment (\.modelContext) var context
    
    @ObservedObject var vm: EinkaufViewModel
    @State private var isDone: Bool = false
    @State private var anzahl: Int = 5
    @State private var textFieldText: String = ""
    
    let kategorieName: String
    
    var body: some View {
        NavigationStack {
                    List {
                       ForEach(vm.items) { item in
                            EinkaufItemRowView(name: item.name, anzahl: item.anzahl, isDone: isDone, textFieldText: textFieldText)
                            .padding(.vertical)
                        }
                    }
                    .listStyle(.plain)
            .navigationBarItems(
                trailing: Button(action: { vm.addItem(context: context) },
                                    label: {
                    Image(systemName: "plus.circle")
                        .resizable()
                        .frame(width: 30, height: 30)
                })
                /*.sheet(isPresented: $showSheet) {
                    EinkaufAddView(vm: vm)
                        .presentationDetents([.fraction(0.7)])
                }*/
            )
            .navigationTitle(kategorieName)
        }
    }
}

#Preview {
    EinkaufslisteDetailView(vm: EinkaufViewModel(), kategorieName: "Testname")
}

struct EinkaufItemRowView: View {
    
    @State var name: String
    @State var anzahl: Int
    @State var isDone: Bool
    @State var textFieldText: String
    
    var body: some View {
        HStack {
            Image(systemName: isDone ? "checkmark.circle.fill" : "checkmark.circle")
                .resizable()
                .frame(width: 30, height: 30)
                .foregroundStyle(isDone ? .green : .primary)
                .padding(.horizontal)
                .onTapGesture {
                    isDone.toggle()
                }
            
            TextField(name, text: $textFieldText)
                .strikethrough(isDone ? true : false)
                .font(.title2)
            
            Spacer()
            
                Button(action: {
                    //guard item.anzahl <= 1 else { return }
                    anzahl -= 1
                }, label: {
                    Image(systemName: "minus.circle")
                        .resizable()
                        .foregroundStyle(.red)
                        .frame(width: 30, height: 30)
                })
            
                Button(action: {
                    anzahl += 1
                }, label: {
                    Image(systemName: "plus.circle")
                        .resizable()
                        .foregroundStyle(.green)
                        .frame(width: 30, height: 30)
                })
            
            Text("\(anzahl)")
                .frame(width: 40)
                .padding(.horizontal, 5)
                .font(.title2)
                .bold()
            
        }
    }
}

Maybe I am totally wrong but I assume there is a problem at the view where the add Buttons is with the "context:context" passing.


Solution

  • In SwiftData, the concept is a little different.

    Query and ModelContext

    The @Query macro works in the view and keeps data in your view in sync with data in your ModelContext.

    You insert data into the ModelContext and your View updates automatically!

    Here's a diagram from the book "SwiftData Mastery in SwiftUI" that shows the concept:

    Observable Object inserts data into model context and view updates.

    Updated Code

    Here is your modified code with the @Query in the View:

    @Model
    class Einkauf {
        let name: String
        var anzahl: Int
        
        init(name: String, anzahl: Int) {
            self.name = name
            self.anzahl = anzahl
        }
    }
    
    @Observable
    class EinkaufViewModel {
        func addItem(context: ModelContext) {
            let newItem = Einkauf(name: "Neuer Artikel", anzahl: 0)
            context.insert(newItem)
        }
    }
    
    struct EinkaufslisteDetailView: View {
        @Query var items: [Einkauf] // Query on the view
        @Environment (\.modelContext) var context
        @State var vm: EinkaufViewModel
        @State private var isDone: Bool = false
        @State private var anzahl: Int = 5
        @State private var textFieldText: String = ""
        
        let kategorieName: String
        
        var body: some View {
            NavigationStack {
                List {
                    ForEach(items) { item in
                        Text(item.name)
                    }
                }
                .toolbar {
                    Button("", systemImage: "plus.circle") {
                        vm.addItem(context: context)
                    }
                }
                .navigationTitle(kategorieName)
            }
        }
    }
    
    #Preview {
        EinkaufslisteDetailView(vm: EinkaufViewModel(), kategorieName: "Testname")
            .modelContainer(for: Einkauf.self, inMemory: true)
    }
    

    List Updating

    Simulator adding items and updating