0
@Model
class Category: Decodable, Identifiable {
    var id: Int
    var title: String
    
    @Relationship var questions = [Question]()

}   

@Model
class Question: Decodable, Identifiable {
    var id: Int
    var text: String

    @Relationship var category: Category?

    init(id: Int, text: String) {
        self.id = id
        self.text = text
    }
    
}

struct EditCategoryView: View {
    @Environment(\.modelContext) private var modelContext
    @Bindable var category: Category //Bindable because I can also edit the category title on this screen

    var body: some View {
        ScrollView {
                        ForEach(category.questions) { question in
                            HStack {
                                Text(question.text)
                                    .baseTextStyle()
                                
                                Spacer()

                                Button(action: {
                                    delete(question: question)
                                }, label: {
                                    Text("Delete")
                                })
                            }
                            .padding(.horizontal, 8)
                        }
                    }
    }

    func delete(question: Question) {
        modelContext.delete(question)
        try! modelContext.save()
    }
}

Nothing happens visually when the Delete button is pressed. But if I leave the screen and come back, the deleted question will be gone. So the deletion is happening but category is not being observed for some reason.

How can I get the view to respond to the deletion of a Question object, which is a child of Category?

1
  • you need a @Query for Questions Commented Mar 2, 2024 at 16:51

4 Answers 4

1

Till now the change doesn't reflect automatically.

Though your shared code is not compilable, but you need to also manually remove from the array to show the changes immediately in the view.

    func delete(question: Question) {
        guard let index = category.questions.firstIndex(of: question) else {
            return
        }
        category.questions.remove(at: index)
        modelContext.delete(question)
        try! modelContext.save()
    }

Sign up to request clarification or add additional context in comments.

Comments

1

You have a @Bindable for your Category object so this what SwiftUI is observing in your view.

But what happens to the Question object in memory when you delete it is that is marked as deleted but not removed so it remains in the questions array.

To handle this (at least for now because it looks like a bug) is to also remove it from the array in the delete function and then this will trigger a view update since we now have updated category in a way that SwiftUI notices.

func delete(question: Question) {
    if let index = category.questions.firstIndex(of: question) {
        category.questions.remove(at: index)
    }
    modelContext.delete(question)
    try! modelContext.save()
}

Comments

1

Were you able find a solution other than the accepted one, which suggests to manually delete from the modelcontext?

I have a similar setup, where I would want to update the category or one of the questions. So an insert or delete is not sufficient, but there is no method to update an object on the modelContext, only insert and delete.

We must be missing something obvious, but can't put my finger on it...

Comments

0

I'm not sure how this impact performance but with a little bit of boilerplate it works.

@Query properly refresh view when something is deleted from context somewhere else.

Pass PersistentIdentifier of object you'd like to work with in init and build Query using _underscore notation where you filter results by this ID.

struct TasksList: View {
    @Query var tasks: [Task]
    
    init(projectID: PersistentIdentifier) {
        let predicate = #Predicate<Task> {
            $0.project?.persistentModelID == projectID
        }
        
        _tasks = Query(filter: predicate, sort: \.name)
    }
}

struct EditTask: View {
    @Query var tasks: [Task]
    @Environment(\.modelContext) private var context
    
    init(taskID: PersistentIdentifier) {
        let predicate = #Predicate<Task> {
            $0.persistentModelID == taskID
        }
        
        _tasks = Query(filter: predicate, sort: \.name)
    }
    
    func deleteTask(_ task: Task) {
        context.delete(task)
    }
}

If parent is a Project then Task need to have inverse relationship to him.

@Model
final class Project {
    var name: String
    var tasks: [Task]
    
    init(name: String, tasks: [Task] = []) {
        self.name = name
        self.tasks = tasks
    }
}

@Model
final class Task {
    var name: String
    var project: Project? // <--- implicit relationship
    
    init(name: String) {
        self.name = name
    }
}

You can use other ID eg. UUID() instead relationship approach.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.