Post

Replies

Boosts

Views

Activity

Reply to SwiftData duplicates values inside array on insert()
I did minified the code, also pushed to https://github.com/deni2s/SwiftDataTesting, but will repost relevant parts bellow: @Model class Car: Identifiable { var id: UUID = UUID() var name: String var features: [Feature] init(id: UUID = UUID(), name: String, features: [Feature]) { self.id = id self.name = name self.features = features } func copy() -> Car { Car( id: UUID(), name: name, features: features ) } } @Model class Feature: Identifiable { @Attribute(.unique) var id: Int @Attribute(.unique) var name: String @Relationship( deleteRule:.cascade, inverse: \Car.features ) private(set) var cars: [Car]? init(id: Int, name: String, cars: [Car]? = nil) { self.id = id self.name = name self.cars = cars } } @ModelActor actor BackgroundActor: Sendable { func prepareData() { try! modelContext.delete(model: Car.self) let car = Car( name: "BMW", features: [ Feature(id: 1, name: "green"), Feature(id: 2, name: "slow") ] ) modelContext.insert(car) try! modelContext.save() } func test(name: String = "BMW") { let fetchDescriptor = FetchDescriptor<Car>( predicate: #Predicate<Car> { car in true } ) let cars = try! modelContext.fetch( fetchDescriptor ) let car = cars.first! print("expected car features:\n\t", car.features.map{$0.name}) //prints ["slow", "green"] - expected let newCar = car.copy() print("expected newCar features:\n\t", newCar.features.map{$0.name}) //prints ["slow", "green"] - expected modelContext.insert(newCar) // newCar.features = car.features //this workaround helps! print("UNEXPECTED newCar features:\n\t", newCar.features.map{$0.name}) //prints ["slow", "green", "slow", "green"] - UNEXPECTED! /*some code planned here modifying newCar.features, but they are wrong here causing issues, for example finding first expected green value and removing it will still keep the unexpected duplicate (unless iterating over all arrays to delete all unexpected duplicates - not optimal and sloooooow). for i in 0..<newCar.features.count { if newCar.features[i].name == "green" { newCar.features.remove(at: i) print("this should remove the green feature") break //feature expected to be removed -> no need to continue iterations } } */ try! modelContext.save() print("after save newCar features:\n\t", newCar.features.map{$0.name}) //prints ["slow", "green"] - self-auto-healed??? } } So still wondering why after inserting (and before saving) car's features array gets duplicated values with duplicated ids. Is this a bug or some strange feature?
Mar ’25
Reply to SwiftData duplicates values inside array on insert()
[quote='828848022, joadan, /thread/776358?answerId=828848022#828848022, /profile/joadan'] And in CarData you need to do something similar but loop over the features array and copy each element. [/quote] I don't think that makes any sense, as Features are identifiable references. I don't need two instances of "green" feature, I need just one, to which two carData instances are referring.
Mar ’25
Reply to SwiftData duplicates values inside array on insert()
[quote='828848022, joadan, /thread/776358?answerId=828848022#828848022, /profile/joadan'] Perhaps unrelated but why do you need both Car and CarData, can't they be merged and personally I prefer to use the @Relationship property wrapper for my relationship properties to make the intention clearer [/quote] About merging properties - this is just minimum mock project to showcase the issue. Real data objects are much more complicated and not related to the cars. There are 20 interconnected objects, equivalent to Car object has 6 properties and equivalent to CarData - like 40 properties, 30 of them being arrays. Maybe I could even use 2 models for showcasing the issue instead of 3...
Mar ’25
Reply to SwiftData duplicates values inside array on insert()
[quote='828848022, joadan, /thread/776358?answerId=828848022#828848022, /profile/joadan'] Your copy methods are in my opinion flawed, you create a new instance of the main object but you re-use the relationship object instead of making a new copy of that as well, so called deep copying. So in Car you should have func copy() -> Car { Car( name: "temporaryNewName", carData: carData.copy() //<-- New instance ) } [/quote] The copy methods are such on purpose because sometimes I need copy of Car with carData referencing to the same CarData instance. For example it can be car for different market - like Opel in Europe and Vauxhall in UK - same carData (carData), different name. If you notice I am doing deep copy in the sample code. Notice the newcar.carData = car.carData.copy()
Mar ’25
Reply to SwiftData duplicates values inside array on insert()
As a workaround I can correct the arrays by inserting after modelContext.insert(newCar): newCar.carData.featuresA = car.carData.featuresA newCar.carData.featuresB = car.carData.featuresB and do necessary modifications on correct arrays without doing modelContext.save() after each new object. The code is less maintainable (need to remember now that by adding new CarData property with array value it needs to be copied here), but from performance perspective new objects with this workaround are created and saved in ~9 seconds compared to ~1 minute with previous "working" approach when modelContext.save() needed to be executed after each modelContext.insert(newCar). But I am still looking for clean solution without these hacky workarounds (as real life project atm has more than 30 properties of array values).
Mar ’25
Reply to SwiftData crash when using a @Query sort descriptor with a relationship
Running into similar issue here. Adding error from console so other devs can find this when searching. SwiftData/Schema.swift:305: Fatal error: KeyPath \Caliber.<computed 0x000000010273b228 (Manufacturer)>.<computed 0x000000010273b230 (String)> points to a field (<computed 0x000000010273b228 (Manufacturer)>) that is unknown to Caliber and cannot be used. From my investigation sort descriptor pointing to a relationship on a model is to blame (doesn't have to be @Query property wrapper, also used in FetchDescriptor crashes). Possible workaround - copy property from relationship to model and sort by it.
Feb ’25
Reply to After upgrading to XCode 16 app stopped working
User should be able to filter CaliberData by specifying size range. #Predicate { caliberData in caliberData.sizeHeights.contains { sizeObject in sizeObject.id >= minSizeHeight && sizeObject.id <= maxSizeHeight } } You are right, that Float is actual size value, but had to wrap it into a custom class as a workaround due to some SwiftData limitation regarding using filtering predicates on value (or relationships (or both)). So using the actual value as 'id' to make it conform to needed Identifiable protocol made sense to save some space.
Oct ’24
Reply to After upgrading to XCode 16 app stopped working
As a workaround until better solution is found this works: import Foundation import SwiftData @Model class CaliberData: Identifiable { var id: UUID = UUID() var sizeHeights: [SizeObject] @Transient var sizeHeightsCopy: [SizeObject] // <-here var featuresABCDIds: [Int] init( id: UUID, sizeHeights: [SizeObject], featuresABCDIds: [Int], ) { self.id = id self.sizeHeights = sizeHeights self.featuresABCDIds = featuresABCDIds sizeHeightsCopy = sizeHeights // <-here } extension CaliberData: Equatable { static func == (lhs: CaliberData, rhs: CaliberData) -> Bool { lhs.featuresABCDIds == rhs.featuresABCDIds && lhs.sizeHeightsCopy == rhs.sizeHeightsCopy // <-here } }
Oct ’24
Reply to SwiftData using Predicate on an Array
if you use collections of value types, e.g. [String], SwiftData will save that directly inside a single property too. Right now it’s encoded as binary property list data, which means you can’t use the contents of your array in a predicate. More: https://www.hackingwithswift.com/quick-start/swiftdata/using-structs-and-enums-in-swiftdata-models
Topic: UI Frameworks SubTopic: SwiftUI Tags:
May ’24