5

I have 2 simple models with relation 1 to many. CarMake can have multiple CarModels but one CarModel can only belong to one CarMake. Pretty straightforward.

@Model
final class CarModel {
 var name: String
 var make: CarMake
 
 init(name: String, make: CarMake) {
 self.name = name
 self.make = make
 }
}
@Model
final class CarMake {
 var name: String
 @Relationship(inverse: \CarModel.make) var models: [CarModel] = []
 
 init(name: String) {
 self.name = name
 }
}

I created a view that will list all CarMakes and another that will list CarModels from the relationship property of CarMake and will also allow me to add a new CarModel to CarMake

struct ContentView: View {
 @Environment(\.modelContext) private var modelContext
 @Query private var items: [CarMake]
 
 @State var count: Int = 1
 var body: some View {
 NavigationStack {
 List {
 ForEach(items) { item in
 NavigationLink(item.name) {
 CarMakeView(make: item)
 }
 }
 }
 .toolbar {
 Button {
 addItem(name: "Car Make \(count)")
 count += 1
 } label: {
 Image(systemName: "plus")
 }
 }
 }
 }
 
 private func addItem(name: String) {
 let newItem = CarMake(name: name)
 modelContext.insert(newItem)
 }
}
struct CarMakeView: View {
 @Environment(\.modelContext) private var modelContext
 
 @State var count: Int = 1
 var make: CarMake
 
 var body: some View {
 List {
 ForEach(make.models) { item in
 Text(item.name)
 }
 }
 .toolbar {
 Button {
 addItem(name: "Car Model \(count)")
 count += 1
 } label: {
 Image(systemName: "plus")
 }
 }
 }
 
 private func addItem(name: String) {
 let newItem = CarModel(name: name, make: make)
 modelContext.insert(newItem)
 }
}

Adding new CarModel object to the modelContext, as presented in private func addItem(name: String) will not cause the list of make.models(the relationship) to update. The data is added to the database correctly because if I restart the app the list will show all the added CarModels from previous app run. Changing screens does not matter, the relationship is not updated until the app is restarted. Can somebody help me to pin point what I'm doing wrong with this code?

I tried to simply add a new object with a relationship to another object and then list all the objects in 1 to many relationship. I expected data to be available as soon as I updated the database.

asked Mar 28, 2024 at 22:04

2 Answers 2

2

This is some kind of bug in SwiftData since you are not doing anything wrong.
The issue is that your CarMakeView has a property make of type CarMake but you are not updating that property directly but via the relationship and this means that the view doesn't see anything dependent being updated and has no reason to update itself.

This is the bug then because make is actually updated when a new car model is added but somehow this change to the relationship property isn't observed properly.

There are some workarounds for this:

The one I use if the relationship properties are optional is to append the new CarModel object to the make property instead because then we update the make property directly

private func addItem(name: String) {
 let newItem = CarModel(name: name)
 make.models.append(newItem)
}

For your case we need to make the relationship property optional in CarModel though for this to work

@Model
final class CarModel {
 var name: String
 var make: CarMake?
 init(name: String, make: CarMake? = nil) {
 self.name = name
 self.make = make
 }
}

Another solution is make the view update for some other reason, in your example I simply made use of an existing property

@State var count: Int = 1

and added it to the body in a Text which will cause the view to redraw

var body: some View {
 VStack {
 Text("\(count)")
 List {
 ForEach(make.models) { item in
 //...
answered Mar 29, 2024 at 7:46
Sign up to request clarification or add additional context in comments.

2 Comments

I didn't want to make my relationship optional as it would go against the definition of my model(the Model cannot exist without Make), but this got me thinking. I've made the relationship implicitly unwrapped optional and assigned it after calling modelContext.insert(newItem). It's not an ideal solution because it forces me to remember to assign the relationship after inserting object, but it keeps optionals out when they should not be possible.
Do you know if this is fixed in Xcode 16 by any chance?
0

There is another workaround to the issue that seems to work. If you make the following change to your CarMakeView, the new items will become visible automatically. I don’t know what it is about @Query that makes this work.

(My answer is over a year after the question, but I wanted to add this for the record.)

@Query private var makes: [CarMake]
var make: CarMake { return makes.first! }
init(make: CarMake) {
 let id = make.id
 // This query will match only one item.
 _makes = Query(filter: #Predicate<CarMake> { 0ドル.persistentModelID == id })
}
answered May 2, 2025 at 9:14

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.