|
Hi all, I'm fairly new to Swift and especially to SwiftData, so I hope someone here will be able to help me understand and hopefully solve an issue I have encountered. My data structure is inspired by the iExpense project (for anyone who is familiar with that project in the 100 days of SwiftUI course). That is, I have an object responsible for loading, saving and editing items, with a single property consisting of an array of those items, and a second model that defines the single item. This all works, except that every time I open the app or add a new item to the array, the order of the array changes in a seemingly random way, and even if I use the append() method, the new element sometimes is inserted at a different position than the end of the array. Here is a shortened version of my models and of the main view, where the loading happens: @Model class Wardrobes { @Relationship(inverse: \Wardrobe.wardrobes) var items: [Wardrobe]? = [] // * func addWardrobe(name: String, importingClothes: [Clothing], selectedWardrobes: [Wardrobe]) { // code to handle adding clothes and style information from other wardrobes to the new wardrobe, which I don't think is relevant for this issue self.items!.append(Wardrobe(id: UUID(), name: name, clothes: copiedClothes, styles: copiedStyles)) } // methods to edit and delete wardrobe objects, which I'll omit because they don't seem to trigger the issue } @Model class Wardrobe: Identifiable, Equatable, Hashable { var wardrobes: Wardrobes? = nil // * var id: UUID = UUID() var name: String = "" var clothes: [Clothing] = [] var styles: [String: MatchInfo] = [:] init(id: UUID = UUID(), name: String = "", clothes: [Clothing] = [], styles: [String: MatchInfo] = [:]) { self.id = id self.name = name self.clothes = clothes self.styles = styles } // methods to add, edit, reorder and delete clothes, and conformances to the protocols listed. Adding a Clothing instance to the clothes array causes the issue when the clothes array was empty. }
struct ContentView: View { @Environment(.modelContext) var modelContext @State var wardrobes: Wardrobes? @State var currentWardrobe = Wardrobe(id: UUID(), name: "Default", clothes: [Clothing]()) var body: some View { NavigationStack { Group { // contains a list displaying the clothes of the current wardrobe, or an UnavailableContentView if currentWardrobe.clothes is empty. } .onAppear { if let wardrobes { print("ContentView appeared") if wardrobes.items!.isEmpty { print("Using default") wardrobes.items!.append(currentWardrobe) } else if currentWardrobe != wardrobes.items![settings.lastWardrobe] || currentWardrobe.clothes != wardrobes.items![settings.lastWardrobe].clothes || currentWardrobe.styles != wardrobes.items![settings.lastWardrobe].styles { print("Setting current wardrobe") currentWardrobe.id = wardrobes.items![settings.lastWardrobe].id currentWardrobe.name = wardrobes.items![settings.lastWardrobe].name currentWardrobe.clothes = wardrobes.items![settings.lastWardrobe].clothes currentWardrobe.styles = wardrobes.items![settings.lastWardrobe].styles } } } .onAppear { // load wardrobes let wardrobesRequest = FetchDescriptor<Wardrobes>() let wardrobesData = try? modelContext.fetch(wardrobesRequest) wardrobes = wardrobesData?.first ?? Wardrobes() print("Loaded wardrobes:") if let items = wardrobes?.items { for w in items { print("- (w.name)") // There is a dedicated view that displays the list of wardrobes and allows to add new ones, but the random reordering is already visible here, suggesting that said view is not the culprit } } } } } I thought this could be some kind of SwiftData bug, so I tried to reproduce the issue in a simplified context: @Model class Items { @Relationship(inverse: \Item.items) var items: [Item]? = [] init(items: [Item] = []) { self.items = items } } @Model class Item { var items: Items? = nil var name: String = "" init(name: String = "") { self.name = name } } struct ContentView: View { @Environment(.modelContext) var modelContext @State var items: Items? var body: some View { NavigationStack { List { if items != nil { ForEach(items!.items!) { item in Text(item.name) } } } .navigationTitle("SwiftData Test") .toolbar { Button("Test") { items!.items!.append(Item(name: "Test")) } } .onAppear { print("Loading Items...") let request = FetchDescriptor<Items>() let data = try? modelContext.fetch(request) items = data?.first ?? Items() items!.items!.append(Item(name: "A")) items!.items!.append(Item(name: "B")) items!.items!.append(Item(name: "C")) } } } } But this sample crashes (and I can't understand why)! More specifically, it crashes on the line "@Relationship(inverse: \Item.items) var items: [Item]? = []". The console prints "Loading Items..." and stops there: no fatal error or such, so I have no idea what's causing the crash. The call stack in the debug navigator shows the following: 0 specialized createModel #1 <τ_0_0><τ_1_0><τ_2_0><τ_30>(:) in createToManyRelationship #1 <τ_0_0><τ_1_0><τ_20>(:) in static _DefaultBackingData.getRelationship<τ_0_0>(key:managedObject:forType:) 5 PersistentModel.getValue<τ_0_0, τ_0_1>(forKey:) 6 Items.items.getter 7 Items.items.modify 8 closure #3 in closure #1 in ContentView.body.getter 9 ___lldb_unnamed_symbol162713 37 static SwiftDataTestApp.$main() 38 main 39 start I encountered this crash in the main project as well, during my several attempts at resolving the issue. Somehow I managed to make it go away there, but the problem where the array changes randomly still persists. If someone more experienced than me would be so kind as to help me understand what's going on here, I would be grateful, because I'm really stumped! If you need more information just let me know, I'll be happy to provide it. Thanks for reading! |
|
hi Laura, it's difficult to diagnose the code itself ... formatting would help: enter a new line with only three back-ticks, go to the next line, paste your code in, then end with a new line having only three back-ticks ... or you can use the
even though SwiftData surfaces a one-to-many relationship in Swift as an array of, say, if you want the that said, there's lots more to consider as you get into SwiftData.
finally, here's what your sample code looks like when you strip out the centralized object that keeps a list of all the other objects:
hope that helps, DMG |
|
@delawaremathguy Apologies for the formatting issue: this was my first post here, plus I’m using VoiceOver and the message editor doesn’t seem to be fully accessible. Anyway, thanks a lot for the clarification about the underlying set used by Core Data: that certainly explains why I was getting that weird behaviour! Also, thanks for the tip about using a standard array with @Query instead of a custom object: I was starting to think that wasn’t the best approach. And as for the naming, you are probably right there too, even though that items.items thing was just a quick example I put together without much thinking (which in fact also caused me to completely forget that I have to insert new objects into the model context 😂). Thanks again for your help!@ò |
SPONSORED Still waiting on your CI build? Speed it up ~3x with Blaze - change one line, pay less, keep your existing GitHub workflows. First 25 HWS readers to use code HACKING at checkout get 50% off the first year. Try it now for free!
Sponsor Hacking with Swift and reach the world's largest Swift community!
You need to create an account or log in to reply.
All interactions here are governed by our code of conduct.
Link copied to your pasteboard.