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:
When you iterate through the user with
ForEach
, you are attaching one.alert()
modifier to each single instance. This means, whenselectDelete
is set totrue
, all of the instances try to show an alert, but only one will. Which one? Who knows, but that instance will be deleted.You have a nice
selectedUsers
variable that changes when you tap on it. But you are deleting theuser
, notselectedUser
.
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
, notuser
:
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 toIdentifiable
or useForEach(_:id:content:)
and provide an explicitid
!
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
Display Table View When Searchbar (From Searchcontroller) Begin Edited Swift
"Message from Debugger: Unable to Attach" When Running Tests on Osx App
Name Convention for Unwrapped Value in Swift
Swift Optionals - Why Does Var A:Int? A? = 4 Return Nil
Pattern Matching in a Swift for Loop
How to Combine Two Nsdictionary in Swift
How to Calculate the Energy Per Bin in a Dft
In Swift, Can One Use a String to Access a Struct Property
Swiftui - Show the Data Fetched from Firebase in View
Differences Generic Protocol Type Parameter VS Direct Protocol Type
Set Insets to the Collectionview Programmatically in Swift
How to Pass/Get Core Data Context in Swiftui Mvvm Viewmodel
Enum Not Working in Custom Initializer
How to Get Rid of Array Brackets While Printing