Skip to content

Instantly share code, notes, and snippets.

@helje5
Created September 13, 2023 21:36
Show Gist options
  • Save helje5/34ff060339566a5bc291ad6a4b7d6edb to your computer and use it in GitHub Desktop.
Save helje5/34ff060339566a5bc291ad6a4b7d6edb to your computer and use it in GitHub Desktop.
SwiftData Inverse Relationships
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)
}
}
@helje5
Copy link
Author

helje5 commented Sep 13, 2023

On startup, no relationships are setup:
Simulator Screenshot - iPhone 15 Pro - 2023-09-13 at 23 52 10 Medium

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)
Simulator Screenshot - iPhone 15 Pro - 2023-09-13 at 23 52 26 Medium

Pressing "refresh" makes the (already established) change show up on the inverse as well.
Simulator Screenshot - iPhone 15 Pro - 2023-09-13 at 23 52 32 Medium

@helje5
Copy link
Author

helje5 commented Sep 27, 2023

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