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)
}
}
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.