SwiftUI / .onDelete child OutlineGroup row in a List from a nested array

Viewed 213

I'm creating a List that renders a nested array using OutlineGroup. Im trying to implement .onDelete to this ForEach loop.

Interface UI

.onDelete is not working properly on this ForEach loop.

It can delete top-level rows correctly, but it does not delete child-level rows correctly.

I think IndexSet is not correctly targeting focusExample[subFocus]. (it gives an error when I try)

How can I get .onDelete to work correctly in this OutlineGroup?

struct FocusArea: Identifiable {
    let id = UUID()
    let focus: String
    let time: Int
    let subFocus: [FocusArea]?
    
}

struct FocusView: View {
 @State private var focusExample =
    [   FocusArea(focus: "PhD", time: 20, subFocus: [
                FocusArea(focus: "Experimenting", time: 15, subFocus: nil),
                FocusArea(focus: "Writing", time: 5, subFocus: nil)
            ]),

        FocusArea(focus: "Reading", time: 10, subFocus:[
                FocusArea(focus: "Holes", time: 7, subFocus: nil),
                FocusArea(focus: "Harry Potter", time: 3, subFocus: nil)
            ]),

        FocusArea(focus: "Piano", time: 5, subFocus: nil)
    ]
    
    func delete(at offset: IndexSet) {
        focusExample.remove(atOffsets: offset)
    }

    var body: some View {
        NavigationView{
            List{
                ForEach(focusExample) { focus in
                        OutlineGroup(focus, children: \.subFocus) { subFocus in
                            HStack {
                                Text(subFocus.focus)
                                Spacer()
                                Group {
                                    Text("\(focus.time)")
                                        .font(Font.system(.body, design: .rounded).weight(.medium))
                                    +
                                    Text(" HR")
                                        .font(Font.system(.body, design: .rounded).weight(.medium).smallCaps())
                                }.foregroundColor(Color.green)
                            
                            }
                            
                        }
                }.onDelete(perform: delete)
 
            }
            .toolbar {
                EditButton()
            }
            .navigationTitle("Focus")
        }
        
    }
}
1 Answers

OutlineGroup is a full view in itself. It doesn't need a ForEach around it.
(Actually in your code you could delete the ForEach and everything will work as before.)

That also means, that (so far) it does not handle Edit Mode nor .onDelete.

BUT: If you only need two hierarchy levels (parents - children) you can do it with ForEach and DisclosureGroup and get to the result you want :)

var body: some View {
    NavigationView{
        List{
            // outer ForEach for parents, binding! for delete to work
            ForEach($focusExample) { $focus in
                DisclosureGroup {
                    // Inner ForEach for children
                    ForEach(focus.subFocus ?? []) { subFocus in
                        focusCell(focus: subFocus)
                    }
                    .onDelete { indices in
                        // delete for children
                        focus.subFocus?.remove(atOffsets: indices)
                    }

                } label: {
                    focusCell(focus: focus)
                }
            }
            .onDelete { indices in
                // delete for parents
                focusExample.remove(atOffsets: indices)
            }
        }
        .toolbar {
            EditButton()
        }
        .navigationTitle("Focus")
    }
}

// your original formatting, just factored out
func focusCell(focus: FocusArea) -> some View {
    HStack {
        Text(focus.focus)
        Spacer()
        Group {
            Text("\(focus.time)")
                .font(Font.system(.body, design: .rounded).weight(.medium))
            +
            Text(" HR")
                .font(Font.system(.body, design: .rounded).weight(.medium).smallCaps())
        }.foregroundColor(Color.green)
    }
}
Related