Swiftui I Can Not Delete Item

SwiftUi I can not delete item

The problem is in ForEach

As mention in the apple doc

ForEach : The instance only reads the initial value of the provided data and doesn’t need to identify views across updates.

So when you remove an object from the array, you have changed the array but SwiftUI doesn't see that change and it uses the original array.

So, you just fix it by just adding conditions like this index < self.model.items.count ? self.model.items[index].value : ""

Also, I suggest using .indices in ForEach

One more point you no need to find index every time on delete. Just pass the index to function.

Here is the complete code.

struct ContentView: View {
@StateObject var model = MyViewModel()
var body: some View {
VStack {
Button(action: {
addItem()
}) {
Text("add")
}
ForEach(model.items.indices, id:\.self) { index in //< -- Here
HStack {
TextField("value", text: Binding(get: { index < self.model.items.count ? self.model.items[index].value : "" }, //<-- Here
set: { self.model.items[index].value = $0 }))
Button(action: {
removeItem(at: index) //< -- Here
}) {
Text("delete")
}
}
}
}
}
func addItem() {
self.model.items.append(MyModel(title: "", value: ""))
}

func removeItem(at index: Int) {
model.items.remove(at: index) //< -- Here
}
}

SwiftUI: cannot delete Row in List

The problem is that you are not using your items directly in the ForEach loop. Consider using structs for your data as objects and make them identifiable.

struct Player : Identifiable {
let id = UUID()
var stepperWerte: Int
var editText : String
}

struct ContentView : View {
@State var isEditing = false

@State var players = [Player(stepperWerte: 3, editText: "Player 1"), Player(stepperWerte: 5, editText: "Player 2"), Player(stepperWerte: 7, editText: "Player 3"), Player(stepperWerte: 9, editText: "Player 4")]

var startName = "new Player"
var startLeben = 5

var body: some View {
NavigationView {
List() {

ForEach(self.players) { player in
SecondView(player: player)
}
.onDelete(perform: spielerLoeschen)
}
.frame(width: nil, height: nil, alignment: .trailing)
.navigationBarTitle(Text("Nav_Title"))
.navigationBarItems(leading: Button(action: { self.isEditing.toggle() }) { Text(isEditing ? "Done" : "Edit").frame(width: 85, height: 40, alignment: .leading) },
trailing: Button(action: spielerHinzufuegen, label: { Image(systemName: "plus") }) )
.environment(\.editMode, .constant(self.isEditing ? EditMode.active : EditMode.inactive)).animation(Animation.spring())
}
}

func spielerLoeschen(at offsets: IndexSet) {
players.remove(atOffsets: offsets)
}
func spielerHinzufuegen() {
players.insert(Player(stepperWerte: 4, editText: "Neuer Player"), at: 0)
}
}

struct SecondView : View {

var player : Player

@State var stepperWerte : Int
@State var name : String

init(player : Player)
{
self._stepperWerte = State(initialValue: player.stepperWerte)
self._name = State(initialValue: player.editText)

self.player = player
}

var body: some View
{
Stepper(value: self.$stepperWerte, in: -1...10, step: 1, label: {
TextField("", text: self.$name)
.layoutPriority(1)
.fixedSize(horizontal: true, vertical: false)
Text("\(player.stepperWerte)")
})
}

}

I created a struct Player, and then an array of many Players. In the ForEach you can directly use players as Player confirms to Identifiable protocol. This is way easier as you can access a player object in your ForEach loop and you do not have to access everything with indices. In the deleting function you just delete the object out of the array or add something new to it. Deleting now works fine.

I have removed some code from the list row, just to reproduce it easier, just if you are wondering.


Why alert dialog delete the wrong item in SwiftUI?

There are 2 problems in your code that are creating the unexpected behaviour:

  1. When you iterate through the user with ForEach, you are attaching one .alert() modifier to each single instance. This means, when selectDelete is set to true, all of the instances try to show an alert, but only one will. Which one? Who knows, but that instance will be deleted.

  2. You have a nice selectedUsers variable that changes when you tap on it. But you are deleting the user, not selectedUser.

How you can fix your code to work with .alert() in 3 steps:

  • if you don't need to perform any task with the tap gesture, just delete it and change the selectedUser in your context menu:
.contextMenu {
Button(action: {

selectedUsers = user // Change here then delete

self.delete(item: data)
}) {
Text("remove") }}

// Forget about it...
//.onTapGesture {
// selectedUsers = user
//}
  • attach your alert to the top-most level of your view (at the bottom):
ScrollView {
...
}
.alert(isPresented: $selectDelete) {
...
}
  • delete selectedUser, not user:
self.delete(item: selectedUser)

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.

How to manually remove an item from List in SwiftUI?

The cause is given to you in an error:

count (1) != its initial count (2). ForEach(_:content:) should only be used for constant data. Instead conform data to Identifiable or use ForEach(_:id:content:) and provide an explicit id!

Instead, use an Identifiable version and operate directly on the content:

    List {
ForEach(wishList, id: \.self) { content in
HStack {
Text(verbatim: content)

Button(action: {
guard let index = self.wishList.firstIndex(of: content) else { return }
self.wishList.remove(at: index)
}) {
Image(systemName: "minus.circle").foregroundColor(.red)
}
}
}
}

EDIT: Here's a simpler version:

    List(0..<wishList.count, id: \.self) { index in
HStack {
Text(verbatim: self.wishList[index])

Button(action: {
self.wishList.remove(at: index)
}) {
Image(systemName: "minus.circle").foregroundColor(.red)
}
}
}

Delete item from a section list on SwiftUI

You’ll need to pass in a section index as well as the row index, so that you know which nested item to delete. Something like this.

.onDelete { self.deleteItem(at: $0, in: sectionIndex) }

And change your function to accept that section index:

func deleteItem(at offsets: IndexSet, in: Int)

In your case you can probably pass in something like territorie.id as the section index, and use that to delete the correct item. Or pass in the territorie object - whatever you need to get to the correct user. Only the index won’t get you there. Hope it all makes sense!

Indices don't update when deleting item from list

The initializer for ForEach that takes in a range can only be used for constant data.

From Apple's docs:

The instance only reads the initial value of the provided data and
doesn’t need to identify views across updates.

Use one of the other ForEach initializers, such as:

ForEach(item_list.enumerated(), id: \.self) { idx, element in

Unable to remove documents from Firebase / Firestore, using SwiftUI

Thanks xTwisteDx and jnpdx for the help answers.

As mentioned by jnpdx, and as I was guessing, I needed to pass the item index into onDelete. But for that, it was necessary to create a new ViewModel (rowViewModel), and instantiate it inside fileViewModel, as in the approach presented by Peter Friese.

In my case, the code is presented bellow.

In ContentView:

           List{
ForEach (fileViewModel.rowViewModel) { data in
VStack {
Text(data.itemOne)
Text(data.itemTwo)
}
}.onDelete { indexSet in self.fileViewModel.deleteItem(atOffsets: indexSet)}

In FileModel: the same as above.

In FileViewModel:

func deleteItem(atOffsets indexSet: IndexSet) {
let viewModels = indexSet.lazy.map { self.rowViewModel[$0] }
viewModels.forEach { rowViewModel in
fileRepository.removeItem(rowViewModel.row) }
}

In FileRepository:

func removeItem(_ fileModel: FileModel) {
if let fileModelID = fileModel.id {
db.collection("fileModels").document(fileModelID).delete() { err in
if let err = err {
print("Error removing document: \(err)")
} else {
print("Document successfully removed!")
}
}
}
}

Again, thanks a lot for all clues.



Related Topics



Leave a reply



Submit