Order is not preserved, meaning the Array that exists in memory is not likely the one that will get persisted and reloaded. That's not really conceptually an Array—that's halfway to a Set.
I'm not seeing a general purpose solution on the Apple Developer Forums yet, and I haven't thought of one myself yet. Have you?
let items = try! modelContext.fetch(FetchDescriptor<T>(sortBy: []))
This returns "array" items in arbitrary order, unless you specify a sort descriptor.
Doubly linked list if you need to remove arbitrary items?
Edit:
Wait, if you don't need O(1) lookup behaviour for your "arrays" (how else could you tolerate having a list?) maybe you just sort your items by a string ID as in vocabulary:
E.g. you have two items: A Z
When inserting third that would become: A M Z
If you have this list: A B ... Z
and need to insert an item between A and B then it becomes: A AM B Z
and so on.
And the key (ID) could be not a string but a more optimal data structure, e.g. an array of UInt8.
On the second thought, doubly linked list with fixed sized ID could be more efficient.
I've just discovered this issue today while seeing my SwiftUI lists reorder randomly, after attempting to insert a new item at the front of a model "stored" array.
I haven't seen any indication from the docs or API that this is intended behavior from SwiftData. Yet, this is noticeably absent as a known issue from the Xcode beta release notes. See here: SwiftData Known Issues
User-decided ordering is critical for my use-case, so I'll be trying out some workarounds until Apple (hopefully) fixes this before release.
In the meantime, I'll be sending a bug report. The more the merrier!
import SwiftUI
import SwiftData
@Model final class Card {
var id: String?
var key: Double
init(id: String, key: Double) {
self.id = id
self.key = key
}
}
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query(sort: \.key, order: .forward) var cards: [Card]
@State var selectedID: String?
var body: some View {
NavigationView {
List {
ForEach(cards) { card in
HStack {
Text(card.id ?? "")
Spacer()
Text(String(card.key)).font(.subheadline).foregroundColor(.secondary)
}.background(card.id == selectedID ? Color.red : Color.clear)
}
}
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button("+10") {
withAnimation {
for i in 0 ..< 10 {
let newCard = Card(
id: String(i),
key: Double(i)
)
modelContext.insert(newCard)
}
}
}
Button {
withAnimation {
if !cards.isEmpty {
// delete random card
let i = Int.random(in: 0 ..< cards.count)
modelContext.delete(cards[i])
selectedID = cards[i].id
}
}
} label: {
Image(systemName: "minus")
}
.disabled(cards.isEmpty)
Button {
withAnimation {
// insert a new card in a random position
let count = cards.count
let i = count == 0 ? 0 : Int.random(in: 0 ..< count)
let prev = count == 0 ? 0 : cards[i].key
let next = i + 1 < count ? cards[i + 1].key : prev + 1
let newKey = (prev + next)/2
let newID = "\(newKey)"
selectedID = newID
modelContext.insert(
Card(
id: "\(newKey)",
key: newKey
)
)
}
} label: {
Image(systemName: "plus")
}
}
}
}
}
}
@main struct CardsApp: App {
var body: some Scene {
WindowGroup { ContentView() }
.modelContainer(for: [Card.self])
}
}
[/details]
I'm using a double key for simplicity just to test the idea. In real app that uses this strategy the key must be variable length (array of UInt8 will do).
On this one I am not sure how to proceed with the query to return items in the list order.
SwiftData, like SwiftUI, is an Apple platform framework, and questions that are just about using the framework should be asked over at the Apple developer forums.