Deleting CoreData from SwiftUI List with Sections
Asked Answered
F

2

9

Goal

I want to delete an item from a SectionedFetchRequest on a ForEach inside a List. The only solutions I have found are for a regular FetchRequest I have managed to delete it from the UIList but not from the CoreData's ViewContext.

My question is unique because I'm trying to delete from a SectionedFetchRequest which is different than a FetchRequest

    @SectionedFetchRequest(entity: Todo.entity(), sectionIdentifier: \.dueDateRelative, sortDescriptors: [NSSortDescriptor(keyPath: \Todo.dueDate, ascending: true)], predicate: nil, animation: Animation.linear)
    var sections: SectionedFetchResults<String, Todo>
    var body: some View {
        NavigationView {
            List {      
                ForEach(sections) { section in
                    Section(header: Text(section.id.description)) {
                        ForEach(section) { todo in
                            TodoRowView(todo: todo)
                                .frame(maxWidth: .infinity)
                                .listRowSeparator(.hidden)
                        }
                        .onDelete { row in
                            deleteTodo(section: section.id.description, row: row)
                            }
                        }

                    }
                }
    func deleteTodo(section: String, row: IndexSet) {
        // Need to delete from list and CoreData viewContex.
    }
// My old way of deleting notes with a regular fetch Request
func deleteNote(at offsets: IndexSet) {
    for index in offsets {
        let todo = todos[index]
        viewContext.delete(todo)
    }
    try? viewContext.save()
}
Facsimile answered 17/12, 2021 at 22:35 Comment(4)
Does this answer your question? Delete data from CoreDataPurport
@loremipsum Hmmm I'm not sure in my context how to utilize that answer as I am using a ForEach loop. I haven't seen a delete solution for a sectionedFetchRequest yet.Facsimile
That answer is the most practical you can use swipeActions with that method. You can still use indexSet as you are but you have to do it for the array in the section. It does not work with all the objects.Purport
@loremipsum tried that solution but what would I put in my context for the .onDelete ? I don't know what to pass into that function or access the todo.Facsimile
P
18

This is how you would use the link...

Add this to the TodoRowView(todo: todo)

.swipeActions(content: {
    Button(role: .destructive, action: {
        deleteTodo(todo: todo)
    }, label: {
        Image(systemName: "trash")
    })
})

And you need this method in the View

public func deleteTodo(todo: Todo){
    viewContext.delete(todo)
    do{
        try viewContext.save()
    } catch{
        print(error)
    }
}

Or you can use your current setup that uses onDelete on the ForEach

.onDelete { indexSet in
    deleteTodo(section: Array(section), offsets: indexSet)
    }

That uses this method

func deleteTodo(section: [Todo], offsets: IndexSet) {
    for index in offsets {
        let todo = section[index]
        viewContext.delete(todo)
    }
    try? viewContext.save()
}

And of course for any of this to work you need a working

@Environment(\.managedObjectContext) var viewContext

At the top of your file

Purport answered 18/12, 2021 at 0:0 Comment(4)
It worked! Thanks again. the whole arrays and IndexSet portions is still confusing I guess I'll need to figure it out more.Facsimile
@JustinComstock onDelete provides the set of index items (indexSet) that the user has selected to delete. When swiping it is only one but if you put an edit button on the List you can delete multiple items (that is why you need the loop). Each section has its own ForEach/array of todo items soneed to pass the array to delete it because you would have to search for the specific section.Purport
I'm getting "NSInternalInconsistencyException', reason: 'attempt to delete item 1 from section 0 which only contains 1 items before the update'" with the swipeActions solution when deleting the last item in a section. Not sure what's the reason. 🤔Aureliaaurelian
@Aureliaaurelian no idea, I know this was common in early versions of SwiftUI and older versions of Xcode. Without a Minimal Reproducible Example it is impossible to help you troubleshoot. you can include that in a question someone will help you out.Purport
S
1

I found this question when searching for a neat solution, couldn't find one so thought I'd share my attempt at deleting from a @SectionedFetchRequest.

  var body: some View {
        NavigationView {
            List {      
                ForEach(sections) { section in
                    Section(section.id) {
                        ForEach(section) { todo in
                            TodoRowView(todo: todo)
                                .frame(maxWidth: .infinity)
                                .listRowSeparator(.hidden)
                                .onDelete { indexSet in
                                    deleteTodos(section: section, offsets: indexSet)
                                }
                        }
                        
                    }

                }
            }
            ...

    private func deleteTodos(section: SectionedFetchResults<String, Todo>.Section, offsets: IndexSet) {
        withAnimation {
            
            offsets.map { section[$0] }.forEach(viewContext.delete)

            do {
                try viewContext.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }
Sixth answered 6/2, 2023 at 11:57 Comment(1)
Very neat! I like that it's similar to the original one.Sfax

© 2022 - 2024 — McMap. All rights reserved.