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.
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
//...