Created
September 13, 2023 21:36
-
-
Save helje5/34ff060339566a5bc291ad6a4b7d6edb to your computer and use it in GitHub Desktop.
SwiftData Inverse Relationships
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
import SwiftData | |
@Model final class Address { | |
let street = "Am Geldspeicher 1" | |
var contact : Contact? | |
init() {} | |
} | |
@Model final class Contact { | |
let name = "JSON Bourne" | |
var addresses : [ Address ] = [] | |
init() {} | |
} | |
struct ContentView: View { | |
@Environment(\.modelContext) private var modelContext | |
@State private var address = Address() | |
@State private var contact = Contact() | |
@State private var reloader = 0 // helper to force refresh all views | |
struct ShowAddress: View { | |
let address: Address | |
let reloader : Int | |
var body: some View { | |
VStack { | |
Text(verbatim: "Address: \(address.persistentModelID.id)") | |
Text(address.street) | |
if let contact = address.contact { Text("Contact: \(contact.name)") } | |
else { Text("No Contact") } | |
} | |
} | |
} | |
struct ShowContact: View { | |
let contact: Contact | |
let reloader : Int | |
var body: some View { | |
VStack { | |
Text(verbatim: "Contact: \(contact.persistentModelID.id)") | |
Text(contact.name) | |
Text(verbatim: "Addresses: \(contact.addresses)") | |
} | |
} | |
} | |
var body: some View { | |
VStack { | |
ShowAddress(address: address, reloader: reloader) | |
Divider() | |
ShowContact(contact: contact, reloader: reloader) | |
Divider() | |
// Those properly add the inverse! But don't trigger Observation. | |
Button("Add Address to Contact") { contact.addresses.append(address) } | |
Button("Add Contact to Address") { address.contact = contact } | |
// Trigger a refresh of all views, makes the updates visible | |
Button("Reload") { reloader += 1 } | |
} | |
.onAppear { | |
modelContext.insert(address) | |
modelContext.insert(contact) | |
} | |
} | |
} | |
@main struct TestApp: App { | |
let container = try! ModelContainer( | |
for: Contact.self, Address.self, | |
configurations: .init(isStoredInMemoryOnly: true) | |
) | |
var body: some Scene { | |
WindowGroup { ContentView() } | |
.modelContainer(container) | |
} | |
} |
It was pointed out that explicitly setting the relationship on both sides fixes the issue, e.g.:
Button("Add Address to Contact") {
contact.addresses.append(address)
address.contact = contact
}
That makes it work. Because the explicit setter is tracked by Observation. (the relationship is set already!)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
On startup, no relationships are setup:
After pressing "Add address to contact", the contact view refreshes, but the address one doesn't, despite that the address view observes the contact relationship (and the contact inverse is properly set)
Pressing "refresh" makes the (already established) change show up on the inverse as well.