0

I am fairly new to the iOS programming world and definitely new to SwiftData, so hopefully this isn't too much of a stupid question...

My SwiftData Schema consists of a one to many to many model. A project can have many reports, and each report can have many items. Project reports are unique to said project, and report items are unique to said report.

Where my issue is, I am trying to pass a list of reports of a specific project into a for each view in a list and then using the .onDelete method to call a function to delete a report out of the list. The problem is that quite often one of the reports I try to delete ends up 'coming back' whenever I navigate out of the view and back in, close the app, etc. The odd thing is that it doesn't happen every time, and it only happens when I delete 2 or more Reports.

Here is my code to (attempt to) do such that:

import SwiftUI
import SwiftData

struct ActiveReports: View {
    @Environment(\.modelContext) private var context
    @State private var alertSaveError = false

    let projectReports: [Report]
    
    var body: some View {
        Group {
            List {
                ForEach(projectReports) { report in
                    NavigationLink {
                        ReportListView(report: report)
                    } label: {
                        HStack{
                            Text("\(report.scope)")
                            Text("Version: \(report.version)")
                            Spacer()
                            Text("\(report.dateCreated.formatted(date: .abbreviated, time:
                                .omitted))")
                        }
                    }
                }
                .onDelete(perform: deleteReport)
            }
        }
        .alert("Failed to Save", isPresented: $alertSaveError ){
            Button("OK", role: .cancel) { }
        }
    }
    
    func deleteReport(_ indexSet: IndexSet) {
        for item in indexSet {
            let report = projectReports[item]
            context.delete(report)
            do{
                try context.save()
            } catch {
                alertSaveError = true
                print("Error in saving")
            }
        }
    }
}

My Project model:


import Foundation
import SwiftData

@Model
class Project {
    var name: String = ""
    var projectNumber: String = ""
    var scopes: [String] = [""]
    
    init(name: String, projectNumber: String, scopes: [String]) {
        self.name = name
        self.projectNumber = projectNumber
        self.scopes = scopes

    }
    
    @Relationship(deleteRule: .cascade)
    var reports: [Report]? = []
}

and my Report model:

import Foundation
import SwiftData

@Model
class Report {
    var version: Int = 1
    var scope: String = ""
    var dateCreated: Date = Date.now
    var issued: Bool = false
    
    init(version: Int, scope: String, issued: Bool) {
        self.version = version
        self.scope = scope
        self.issued = issued
    }
    
    // Setting the relationship back to parent Project
    @Relationship(inverse: \Project.reports)
    var project: Project?
    
    //Setting up the related punch items
    @Relationship(deleteRule: .cascade)
    var punchItems: [PunchItem]?
}

Now I have messed with this a bit and one thing I tried was using the @Query macro to bring in my list of reports instead of passing them into the view. That did work for deleting the reports reliably! The problem is that ends up showing all the reports for all 'Projects' which I don't want. I tried filtering the Query, but then I ran into the issue of not being able to dynamically filter Query results. (Passing in the project name so that I only receive its Reports). Maybe this is the route I should take here, and my knowledge is limiting me.

I also tried forcing a save on the model container just in case SwiftData wasn't automatically committing changes correctly. I never got a failure, but still the issue persisted. I saw that other posts state that similar behavior was a known bug in a previous version (in theory patched) and forcing a save was a work around. This does not seem to fix it for me.

All this makes me think that this is most certainly a logic failure on my end, but for the life of me I cannot figure out why the delete behavior works reliably with @Query macro implemented, vs ~50% of the time when passing the array of items into the view.

EDIT: I am not quite sure why this is the case, but I switched my .onDelete method to a .swipeActions on the items within the ForEach loop and the delete behavior works correctly now. Bug? Maybe, but more likely a lack of understanding on my end.

3
  • Where does an actor named projectReports come from in the SwiftUI ActiveReports struct? And how does SwiftData come into play in it? Commented Feb 3, 2024 at 5:13
  • What happens if you only call context.save() once after the for loop? Commented Feb 3, 2024 at 8:17
  • @ElTomato, The projectReports array is passed in from the parent view. It is the set of Reports that is the property of a parent Project. So, the one Project has an array of Reports that are unique to the Project. I am trying to use swiftData to persist the Project, Reports, and Items(Added as an array to each Report) as my user creates and modifies them. Commented Feb 3, 2024 at 16:11

2 Answers 2

0

Method context.delete(_ model: PersistentModel) never really deleted the data for me even I called context.save() after it. This is my solution which use Predicate:

    let key = dataToBeDeleted.id 

    let predicate = #Predicate<MyDataModel> { d in
        d.id == key
    }

    context.delete(model: MyDataModel.self, where: predicate)
    context.save()

The data model should has an unique id:

@Model
class MyDataModel : Identifiable {
    @Attribute(.unique)
    let id: String
  
}
Sign up to request clarification or add additional context in comments.

Comments

-1

After attempting to implement solutions that others have suggested, I found another option of using swipe action to delete as follows. Others may be able to chime in as to why this works and the previous did not, but for now I am unsure.

import SwiftUI
import SwiftData

struct ActiveReports: View {
    @Environment(\.modelContext) private var context
    
    let projectReports: [Report]
    
    @State private var alertSaveError = false
    
    var body: some View {
        Group {
            List {
                ForEach(projectReports) { report in
                    NavigationLink {
                        ReportListView(report: report)
                    } label: {
                        HStack{
                            Text("\(report.scope)")
                            Text("Version: \(report.version)")
                            Spacer()
                            Text("\(report.dateCreated.formatted(date: .abbreviated, time: .omitted))")
                        }
                    }
// I added this swipe action calling the same delete function as the previous.onDelete method. 
                    .swipeActions {
                        Button(role: .destructive) {
                            withAnimation {
                                context.delete(report)
                            }
                        } label: {
                             Text("Delete")
                        }
                    }
                }
                // And removed this following .onDelete method.
                //.onDelete(perform: deleteReport)
            }
        }
        .alert("Failed to Save", isPresented: $alertSaveError ){

            Button("OK", role: .cancel) { }
        }
    }
}

1 Comment

The major difference and what you yourself said was the cause of the problem is that you deleted many objects at once with your previous solution but here you only delete one at a time. Also you are not calling save() after each delete but lets SwiftData handles this, although I don't think this is as relevant to the issue as deleting one versus deleting many objects at once.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.