GO FURTHER, FASTER: Try the Swift Career Accelerator today! >>

SOLVED: SwiftData Relationship woes

Forums > SwiftUI

@atom  

I’d like to model a collection of Stereoview cards in SwiftData, but I don’t think I quite have the hang of things yet.

Each card can have more than one title and more than one author, etc (the data came from the NY Public Library and was originally stored in a Library of Congress metadata format).

So I figured I’d make a model for Card, one for Author, and one for Title, and set up some relationships. Every card should have at least one author and one title, but possibly more. And many cards could have the same author, title, etc.

I’ve set up some sample data, and am trying to use Previews to sort out a layout. It all seemed to go fine the first time, but now 80% of the time I get an error: NSInternalInconsistencyException. Reason: improper model with nonsensical relationship definitions.

Which seems to suggest I’ve set up the relationships between my models incorrectly.

Any suggestions on how to best set this up, and/or what I got wrong?

My models:

import Foundation
import SwiftData

enum CardSchemaV1: VersionedSchema {
    static var versionIdentifier: Schema.Version = .init(0,1,0)

    static var models: [any PersistentModel.Type] {
        [CardSchemaV1.StereoCard.self, TitleSchemaV1.Title.self, AuthorSchemaV1.Author.self]
    }

    @Model
    class StereoCard {
        @Attribute(.unique)
        var uuid: UUID
        @Relationship(inverse: \TitleSchemaV1.Title.cards)
        var titles = [TitleSchemaV1.Title]()
        @Relationship(inverse: \TitleSchemaV1.Title.cards)
        var titlePick: TitleSchemaV1.Title?
        @Relationship(inverse: \AuthorSchemaV1.Author.cards)
        var authors = [AuthorSchemaV1.Author]()

        init(
            uuid: String
        ) {
            self.uuid = UUID(uuidString: uuid) ?? UUID()
        }

        static let sampleData = [
            StereoCard(
                uuid: "c7980740-c53b-012f-c86d-58d385a7bc34"
                ),
            StereoCard(
                uuid: "f0bf5ba0-c53b-012f-dab2-58d385a7bc34"
            )
        ]
    }
}
import Foundation
import SwiftData

enum TitleSchemaV1: VersionedSchema {
    static var versionIdentifier: Schema.Version = .init(1,0,0)

    static var models: [any PersistentModel.Type] {
        [TitleSchemaV1.Title.self, CardSchemaV1.StereoCard.self]
    }

    @Model
    class Title {
        var text: String
        var cards = [CardSchemaV1.StereoCard]()

        init(
            text: String
        ) {
            self.text = text
        }

        static let sampleData = [
            Title(text: "Bird's-eye view, Columbian Exposition."),
            Title(text: "Stereoscopic views of the World's Columbian Exposition. 7972."),
            Title(text: "Stereoscopic views of the World's Columbian Exposition. 8288."),
            Title(text: "Ostrich farm, Midway Plaisance, Columbian Exposition."),
        ]
    }
}

And the sample data function:

import Foundation
import SwiftData

@MainActor
class SampleData {
    static let shared = SampleData()

    let modelContainer: ModelContainer

    var context: ModelContext {
        modelContainer.mainContext
    }

    private init() {
        let schema = Schema([CardSchemaV1.StereoCard.self, TitleSchemaV1.Title.self])

        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

        do {
            modelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration])
            insertSampleData()
        } catch {
            fatalError("Could not create ModelContainer: \(error)")
        }

        print(URL.applicationSupportDirectory.path(percentEncoded: false))

    }

    func insertSampleData() {
        for card in CardSchemaV1.StereoCard.sampleData {
            context.insert(card)
        }

        for title in TitleSchemaV1.Title.sampleData {
            context.insert(title)
        }

        // Add titles
        CardSchemaV1.StereoCard.sampleData[0].titles = [
            TitleSchemaV1.Title.sampleData[0],
            TitleSchemaV1.Title.sampleData[1]
        ]
        CardSchemaV1.StereoCard.sampleData[0].titlePick = TitleSchemaV1.Title.sampleData[0]

        CardSchemaV1.StereoCard.sampleData[1].titles = [
            TitleSchemaV1.Title.sampleData[2],
            TitleSchemaV1.Title.sampleData[3]
        ]
        CardSchemaV1.StereoCard.sampleData[1].titlePick = TitleSchemaV1.Title.sampleData[3]

        // Add authors
        CardSchemaV1.StereoCard.sampleData[0].authors = [AuthorSchemaV1.Author.sampleData[0]]
        CardSchemaV1.StereoCard.sampleData[1].authors = [AuthorSchemaV1.Author.sampleData[0]]

        do {
            try context.save()
        } catch {
            print("Sample data context failed to save")
        }
    }

    var card: CardSchemaV1.StereoCard {
        CardSchemaV1.StereoCard.sampleData[0]
    }

    var cards: [CardSchemaV1.StereoCard] {
        CardSchemaV1.StereoCard.sampleData
    }
}

   

@atom  

It appears that the @Relationship(inverse:... were to blame. SwifData was happy to just detect the releationships on its own. Once I removed those lines, my Previews started working.

   

@atom  

On closer inspection, I think it was having two inverses pointing to the same thing (cards) 🤦

@Relationship(inverse: \TitleSchemaV1.Title.cards)
var titles = [TitleSchemaV1.Title]()
@Relationship(inverse: \TitleSchemaV1.Title.cards)
var titlePick: TitleSchemaV1.Title?

   

It sounds like you're on the right track with modeling your collection of Stereoview cards in SwiftData, but the error you're encountering suggests there might be issues with your model relationships. Without seeing your code, it's a bit tricky to pinpoint the exact problem, but it's possible that the way you've defined your relationships between the Card, Author, and Title models might need adjustment.

Make sure you've correctly set up the relationships to reflect that each card can have multiple authors and titles, and that each author and title can be associated with multiple cards. Double-check your model definitions and the way you're setting up these relationships to ensure they align with your data structure and intended functionality. If you're still having trouble, feel free to share some code snippets or more details about your implementation, and we can try to provide more targeted advice!

by the way today egg rate is 1$

   

Hacking with Swift is sponsored by Alex.

SPONSORED Alex is the iOS & Mac developer’s ultimate AI assistant. It integrates with Xcode, offering a best-in-class Swift coding agent. Generate modern SwiftUI from images. Fast-apply suggestions from Claude 3.5 Sonnet, o3-mini, and DeepSeek R1. Autofix Swift 6 errors and warnings. And so much more. Start your 7-day free trial today!

Try for free!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.