FPL
FPL

Reputation: 466

EditButton() in SwiftUI inside a ScrollView / LazyVGrid

It seems like the EditButton() in SwiftUI (Xcode 12.5 beta 3) has various issues.

In my code, everything was working fine until I replaced the List with a ScrollView and added a LazyVGrid. Now, when a user taps on the EditButton, EditMode is not activated.

Any ideas for a workaround? Having 2 columns is a requirement of the UI, and while I could work with a list I prefer the look of ScrollView. I've tried numerous things... putting the ForEach in a Section and putting the EditButton in the header, replacing it with a manual button... unfortunately none of them seem to work :-(

Many thanks for any thoughts or anything anyone else has done to get round this.

struct Home: View {
    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest(entity: Cars.entity(), sortDescriptors: []) var cars: FetchedResults<Cars>
    
    private var columns: [GridItem] = [
            GridItem(.flexible()),
            GridItem(.flexible())
        ]

    var body: some View {
  
        NavigationView {
            ScrollView {

                if cars.count > 0 {
                    LazyVGrid(
                        columns: columns) {
                
                ForEach(cars) { n in
                    Text("hello")
                }
                .onDelete(perform: deleteCars)
                    }    
                }
                
                else {
                    Text("You have no cars.")
                }   
            } 
            .navigationBarItems(leading: EditButton())       
        }   
    }
    
    func deleteCars(at offsets: IndexSet) {
        for offset in offsets {
            let cars = cars[offset]
            viewContext.delete(cars)
        }
        try? viewContext.save()
    }
    
}

Attempt 1

After reading Asperi's comments below, I have added the following (below) to the ScrollView to manually create the button, trigger EditMode and remove items. Now I am getting a new error on the line deleteCars: "Initializer 'init(_:)' requires that 'FetchedResults.Element' (aka 'Cars') conform to 'Sequence'".

It seems like I am really close, but still struggling - can anyone help me with the final piece of this? Many thanks!

@State var isEditing = false

...

ScrollView {

                if cars.count > 0 {
                    LazyVGrid(
                        columns: columns) {
                        ForEach(cars) { n in
                    Text("hello")
                    Button(action : {
                        deleteCars(at: IndexSet(n))
                        print("item deleted")
                    })
                    
                    {Text("\(isEditing ? "Delete me" : "not editing")")}
                }
                .onDelete(perform: deleteCars)
                .environment(\.editMode, .constant(self.isEditing ? EditMode.active : EditMode.inactive)).animation(Animation.spring())
                    }
                }
                else {
                    Text("You have no cars.")
                }
                Button(action: {
                                    self.isEditing.toggle()
                                }) {
                                    Text(isEditing ? "Done" : "Edit")
                                        .frame(width: 80, height: 40)
                                }
            
            }

Upvotes: 0

Views: 1589

Answers (1)

FPL
FPL

Reputation: 466

TLDR: manually create an EditButton and add it directly inside the ForEach loop. Make sure the ForEach array has indices specified.

Ok, finally found an answer following progress in Attempt 1 above... worked by adding .indices to the array in the ForEach loop. Here goes in full:

struct Home: View {
    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest(entity: Cars.entity(), sortDescriptors: []) var cars: FetchedResults<Cars>

    @State var isEditing = false
    
    private var columns: [GridItem] = [
            GridItem(.flexible()),
            GridItem(.flexible())
        ]

    var body: some View {
  
        NavigationView {
            VStack{   
            ScrollView {

                if cars.count > 0 {
                    LazyVGrid(
                        columns: columns) {
                    ForEach(cars.indices, id: \.self) { n in
                    Text("hello")
                    Button(action : {
                        deleteCars(at: [n])
                        print("item deleted")
                    })
                    
                    {Text("\(isEditing ? "Delete me" : "not editing")")}
                        }
                }
                .environment(\.editMode, .constant(self.isEditing ? EditMode.active : EditMode.inactive)).animation(Animation.spring())
                    }
                }
                else {
                    Text("You have no cars.")
                }        
            }
            .navigationBarItems(leading: Button(action: {
                self.isEditing.toggle()
            }) {
                Text(isEditing ? "Done" : "Edit")
                    .frame(width: 80, height: 40)
            }
            ) 
        }   
    }
    
    func deleteCars(at offsets: IndexSet) {
        for offset in offsets {
            let cars = cars[offset]
            viewContext.delete(cars)
        }
        try? viewContext.save()
    }
    
}

I am sure there are many ways to optimise this (both the code and the way the question/answer are written, so do feel free to suggest them.

Upvotes: 0

Related Questions