Swiftui: Deleting Last Row in Foreach

SwiftUI: Deleting last row in ForEach

Here is fix

ForEach(Array(player.scores.enumerated()), id: \.element) { index, score in
HStack {
if self.isEditSelected {
Button(action: {
self.player.scores.remove(at: index)
}, label: {
Image("delete")
})
}
TextField("\(score)", value: Binding( // << use proxy binding !!
get: { self.player.scores[index] },
set: { self.player.scores[index] = $0 }),
formatter: NumberFormatter())
}
}

SwiftUI : How I can delete row of ForEach in VStack?

The simplest way to implement this is with an optional selection variable and a custom binding for the .actionSheet(). Please see the code below. I commented the important parts. I also changed your code to a Minimal Reproducible Example (MRE) for demonstration purposes.

struct ContentView: View {

@State var tasks = Array(1...20).map( { TaskModel(title: "Task \($0)") })
// This is your optional selection variable
@State var selectedRow: TaskModel?

var body: some View {
VStack {
ForEach(tasks) { task in
Text(task.title)
// put the .onLongPressGesture() on the row itself
.onLongPressGesture {
// set selectedRow to the task
selectedRow = task
}
// This creates a custom binding
.actionSheet(isPresented: Binding<Bool>(
// the get returns the Bool that is the comparison of selectedRow and task
// if they are equal, the .actionSheet() fires
get: { selectedRow == task },
// when done, .actionSheet() sets the Binding<Bool> to false, but we intercept that
// and use it to set the selectedRow back to nil
set: { if !$0 {
selectedRow = nil
}})) {
ActionSheet(
title: Text("Settings"),
// added task.title to prove the correct row is in the ActionSheet
message: Text("Press the button for \(task.title)"),
buttons: [
.cancel(),
.destructive(Text("Delete"), action: {}),
.default(Text("Edit"), action: {})
]
)
}
}
}
}
}

SwiftUI - ForEach deletion transition always applied to last item only

The reason you're seeing this behavior is because you use an index as an id for ForEach. So, when an element is removed from the cards array, the only difference that ForEach sees is that the last index is gone.

You need to make sure that the id uniquely identifies each element of ForEach.

If you must use indices and have each element identified, you can either use the enumerated method or zip the array and its indices together. I like the latter:

ForEach(Array(zip(cards.indices, cards)), id: \.1) { (index, card) in 
//...
}

The above uses the object itself as the ID, which requires conformance to Hashable. If you don't want that, you can use the id property directly:

ForEach(Array(zip(cards.indices, cards)), id: \.1.id) { (index, card) in
//...
}

For completeness, here's the enumerated version (technically, it's not an index, but rather an offset, but for 0-based arrays it's the same):

ForEach(Array(cards.enumerated()), id: \.1) { (index, card) in 
//...
}

In SwiftUI, how do you delete a list row that is nested within two ForEach statements?

I've figured out a solution to my question above, and am posting it here in case anyone else ever struggles with this same issue. The solution involved using .swipeActions() rather than .onDelete(). For reasons I don't understand, I could attach .swipeActions() (but not .onDelete()) to the Text(item.name) line of code. This made the "item" for each ForEach iteration available to the .swipeAction code, and everything else was very straightforward. The revised ContentView code now looks like this:

struct ContentView: View {

@EnvironmentObject var appData: AppData

var body: some View {
List {
ForEach(appData.sortedByCategories, id: \.self) { category in
Section(header: Text(category)) {
ForEach(appData.dictGroupedByCategory[category] ?? []) { item in
Text(item.name)
.swipeActions(allowsFullSwipe: false) {
Button(role: .destructive) {
print(category)
print(item.name)

if let indexToDelete = appData.items.firstIndex(where: {$0.id == item.id }) {
appData.deleteItem(index: indexToDelete)
} // End of action to perform if there is an indexToDelete

} label: {
Label("Delete", systemImage: "trash.fill")
}
} // End of .swipeActions()
} // End of inner ForEach (items within category)
} // End of Section
} // End of outer ForEach (for categories themselves)
} // End of List
} // End of body View
} // End of ContentView

SwiftUI: Index out of range when last element gets removed

I found a solution, I changed my forEach to:

ForEach(checklistItems.indices, id: \.self) { index in
HStack {
RowView(
checklistItem:
Binding(
get: { self.checklistItems[index] },
set: { self.checklistItems[index] = $0 }
),
viewModel: viewModel
)
.padding(.leading, 12)

if checklistEditMode {
Button {
checklistItems.remove(at: index)
} label: {
Image("XCircle")
.resizable()
.frame(width: 18, height: 18)
}
.padding(.trailing, 12)
}
}
.padding(.horizontal, 12)
.padding(.top, 12)
.padding(.bottom, 4)

Divider()
.frame(width: 311)
}

and it works now with no crashes, Thanks to this

how to delete an item in a forEach loop with a delete btn for each of it. I use swiftUI with core data

You don't actually need the index if you're issuing the delete instruction from within your loop, as your NSManagedObjectContext instance has a delete(_:) method that takes the object itself. That change will propagate through your @FetchRequest object automatically, your SwiftUI view will update to show the collection without the now-deleted object.

So your button action becomes:

Button(action: {
viewContext.delete(prod)
}) {
Image(systemName: ...)
// etc.
}

Note that while you'll see the effect straight away, the deletion will only be in memory until you call save on the managed object context.

In my CoreData apps, I tend to save my changes separately, for example when the app is about to go into the background. But if you want to trigger a save immediately the object is removed, that's straightforward enough:

Button(action: {
viewContext.delete(prod)
try? viewContext.save()
}) {
Image(systemName: ...)
// etc.
}

NB: the documentation for NSManagedObjectContext.save() says that you should check the hasChanges property before attempting to save, but as you've just made a change in the line above, that's not necessary in this particular example.

SwiftUI delete rows from a list

The problem is that in your List, the id you give it is \.offset. However, since you are removing data from bgColors, so this data can change. Instead, you should set the id as \.element because it will be constant for each color.

Consider this simplified example, which crashes when you remove a Color from the list:

struct ContentView: View {

@State private var arr: [Color] = [.red, .green, .blue]

var body: some View {
List {
ForEach(Array(arr.enumerated()), id: \.offset) { (index, _) in
ColorPicker("Color", selection: $arr[index])
}
.onDelete(perform: delete)
}
}

private func delete(at offsets: IndexSet) {
arr.remove(atOffsets: offsets)
}
}

And the working example, where the changes are the id given to the List, and the new Binding to the color (note the custom Binding for the selection):

struct ContentView: View {

@State private var arr: [Color] = [.red, .green, .blue]

var body: some View {
List {
ForEach(Array(arr.enumerated()), id: \.element) { (index, _) in
ColorPicker(
"Color",
selection: Binding<Color>(
get: { arr[index] },
set: { arr[index] = $0 }
)
)
}
.onDelete(perform: delete)
}
}

private func delete(at offsets: IndexSet) {
arr.remove(atOffsets: offsets)
}
}

SwiftUI list ForEach index out of range when last array element deleted

I found the solution to my problem. The problem appears to be that the ForEach is trying to look at the difference between the previous and the new data and is trying to grab deleted core data objects. I fixed the problem by changing the ForEach loop to check if the object we are looking at has been deleted

ForEach(Array(viewModel.rooms.enumerated()), id: \.self.element.id) { index, room in
if room.isFault {
EmptyView()
} else {
RoomRow(room: self.viewModel.rooms[index], viewModel: viewModel, index: index)
}
}


Related Topics



Leave a reply



Submit