swiftswiftuiswiftdataswift-data-relationship

Issues with SwiftData One-to-Many Relationships


When a SwiftData @Model has a one-to-many relationship with another @Model, and there are multiple class variables involved, SwiftData seems to struggle with associating each variable with its corresponding data.

Example Code The following code defines two SwiftData models: Book and Page. In the Book class, there is a property contentPage of type Page, and an optional array pages that holds multiple Page instances.

@Model
class Book {
    var id = UUID()
    var title: String
    var contentPage: Page
    var pages: [Page]?
    
    init(id: UUID = UUID(), title: String, contentPage: Page) {
        self.id = id
        self.title = title
        self.contentPage = contentPage
        contentPage.book = self
    }
    
    func addPage(page: Page) {
        if pages == nil {
            pages = []
        }
        page.book = self
        pages?.append(page)
    }
}

enum PageType: String, Codable {
    case contentsPage = "Contents"
    case picturePage = "Picture"
    case textPage = "Text"
    case blankPage = "Blank"
}

@Model
class Page {
    var id = UUID()
    var pageType: PageType
    var pageNumber: Int
    var content: String
    var book: Book?
    
    init(id: UUID = UUID(), pageType: PageType, content: String, pageNumber: Int) {
        self.id = id
        self.pageType = pageType
        self.pageNumber = pageNumber
        self.content = content
    }
}

With the code above, I created a Book instance and populated all fields except for the pages, which was left as nil. However, when I attempt to iterate over the pages, I receive the contentPage instead. This indicates that there may be an issue with how SwiftData handles these associations.

Here is a link to the complete code example


Solution

  • First of all you need to match the number of properties in each type, so if you have two references to Page in Book then you need two references to Book in Page. Second of all you should always use the @Relationship macro when you have multiple relationships between two types because SwiftData can't know which one is which.

    So your models should look something like this

    @Model
    class Book {
        var id = UUID()
        var title: String
    
        @Relationship(inverse: \Page.bookContent) var contentPage: Page
        @Relationship(inverse: \Page.book) var pages: [Page]?
    
        //...
    }
    
    @Model
    class Page {
        var id = UUID()
        var pageType: PageType
        var pageNumber: Int
        var content: String
        var bookContent: Book?
        var book: Book?
    
        //...
    }
    

    I recommend you to read up on the Relationship macro so you can customise your relationships further with for instance delete rules etc.