0

I have a simple test app with a behavior I just don't understand.

import SwiftUI
import SwiftData

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    @Query private var items: [Item]

    var body: some View {
        NavigationView {
            List {
 //               ForEach(items) { item in
                ForEach(items.sorted(by: { $0.sortNr < $1.sortNr })) { item in
                    HStack{
                        Text("\(item.sortNr)")
                        Text("\(item.itemName)")
                    }
                }
                .onDelete(perform: deleteItems)
            }
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    EditButton()
                }
                ToolbarItem {
                    Button(action: addItem) {
                        Label("Add Item", systemImage: "plus")
                    }
                }
            }
        }
    }

    private func addItem() {
        withAnimation {
            let newItem1 = Item(itemName: "one", sortNr: 1)
            let newItem2 = Item(itemName: "two", sortNr: 2)
            let newItem3 = Item(itemName: "three", sortNr: 3)
            let newItem4 = Item(itemName: "four", sortNr: 4)
            modelContext.insert(newItem1)
            modelContext.insert(newItem2)
            modelContext.insert(newItem3)
            modelContext.insert(newItem4)
        }
    }

    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            for index in offsets {
                modelContext.delete(items[index])
            }
        }
    }
}

@Model
final class Item {
    let id = UUID()
    var itemName : String
    var sortNr : Int
    
    init(itemName: String, sortNr: Int) {
        self.itemName = itemName
        self.sortNr = sortNr
    }
}

When I use the commented ForEach loop without the sorted(by:..) the list of items is displayed in some random order. The deleting of items removes the respective selected item correctly. I understand that Query and modelContext.insert does not guarantee a specific order, if not specified.

When I use ForEach loop with the sorted(by..) as above the items are displayed in the sorted manner according to the sortNr. But when I swipe to delete one item, most often a "wrong" item is deleted. The offsets is the correct index of the entry I selected, but it seems like the [item] has different indices in the view body than in the deleteItems func.

What do I need to add/change in this cod to have the correct indices in the array. I can use the @Query(sort... as in my app the input to the List view is a dynamic parameter.

2 Answers 2

2

This really isn't a SwiftData issue at all. You have an array that you are putting into the ForEach in one order, and then you are changing that order within the ForEach, so the array has one order and the ForEach another.

For example, you have these arrays:

[2,3,1] and [2,3,1].sorted(). You actually have

[2,3,1] and [1,2,3]. [2,3,1] is your original and [1,2,3] in in the ForEach

So, when the user swipes to delete "1" in the view that the ForEach supplies, the OS says ok, we need to delete the first element from the array, so it goes to the original array and deletes the first element "2". The solution is simply to sort your original array, and then supply it to the ForEach so you are comparing apples to apples.

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    // Sort the actual query
    @Query(sort: \Item.sortNr) private var items: [Item]

    var body: some View {
        NavigationView {
            List {
              ForEach(items) { item in
                // The rest of your code here
}               

If you weren't allowing deletions, your code would be fine, but you are referencing a variable outside of what you have scoped in the ForEach that has a different set of rules.

Sign up to request clarification or add additional context in comments.

Comments

0

If you want to sort in the ForEach, you should do the same sort in the deleteItems(), as in

private func deleteItems(offsets: IndexSet) {
    withAnimation {
        for index in offsets {
            modelContext.delete(items.sorted(by: { $0.sortNr < $1.sortNr })[index])
        }
    }
}

This ensures that the indices refer to the same objects.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.