SwiftData relationships between elements of the same model – can you control or suppress them?

Part of my app involves a genogram (basically a family tree of genetic relations). That means I really only have 1 model type – a Relative, who has relation types to the person at the core of the tree (the “index” person). I’ve settled on a model of having each person defining their nuclear family (parents, siblings, children, and spouse(s)), in addition to their relation to the index. This would allow the tree to be drawn (when I get that far) by traversing the relationships, and gives an easy way for the user to verify things. It seems necessary to do this way because I don’t just need to know Person B is the aunt of Index, I need to know how – Person B is sister of Person A who is father to index, etc.

Issues

I can’t properly define inverse relationships, because there’s only 1 model – inverse of siblings is siblings, which creates a circular reference. Same for spouses. Inverse of both mom and dad is children – but if you tell SwiftData that, then when you add Parent A is mom of Index, Parent A also gets added as dad.

I left off the relationships and tried to add everything in my savePerson method, and have it add the relationship, the inverse, and other things it can already guess. For example, if you add mom first, then when you add dad to index person, it will automatically put mom and dad as spouses. Then add a sibling to index, and it will also make that person child of mom and dad.

This all looks like it works! Run the app, add a bunch of people, all relations are added as they should be, with the program making assumptions/guesses where appropriate.

But after closing the app and reopening, some relationships are missing.

Examples:

  1. Add Index, then Mom -> Mom and Index are properly inverses of each other and persist on opening/closing; then add Dad and check Mom/Dad are spouses and Index is child of both -> however, on reopening, Mom suddenly has no children
  • same happens in opposite direction – add Dad first and then Mom and everything works, but reopen and Dad will lose Index
  1. Add Mom, Dad, Sister, then Dad’s brother and sister as Aunt and Uncle -> again, you can verify that all relationships work correctly at first -> on reopening, Dad has no siblings, one of Aunt or Uncle has one, the other has (correctly) 2

Forcing modelContext.save() after the savePerson method didn’t help, or surface the bug before reopening. I assume something is happening in SwiftData “correcting” what it thinks the relationships should be – if it were an error in my code itself, I assume I’d see a problem immediately, not on restart? I don’t manipulate the save data on close or restart of the app (at least not deliberately).

Of note, I also tried to re-jig the savePerson method a few ways to do things in a different order, add relationships from only one side (see if the other side would be assumed by SwiftData), etc. – all attempts either caused crashes (I think for invalid inverse relationships) or just led to the same error where things get dropped on restart. Changing where I call modelContext.insert also didn’t matter.

So if SwiftData’s assumptions are causing the issue, is there a proper way to define the necessary relationships when I have only this 1 model? Or stop SwiftData from doing its thing so I can control all relationships manually?

Model and enums

@Model
class Relative {
    let name: String
    let gender: Gender
    let dob: Date
    let isIndex: Bool
    let relationToIndex: RelationType
    var qualifier: RelationQualifier?
    var diagnoses: [String] = []
    var mother: Relative? = nil
    var father: Relative? = nil
    var spouses: [Relative] = []
    var siblings: [Relative] = []
    var children: [Relative] = []
    
    init(name: String, gender: Gender, dob: Date, isIndex: Bool = false, relationToIndex: RelationType, qualifier: RelationQualifier? = nil, diagnoses: [String] = [], mother: Relative? = nil, father: Relative? = nil, spouses: [Relative] = [], siblings: [Relative] = [], children: [Relative] = []) {
        self.name = name
        self.gender = gender
        self.dob = dob
        self.isIndex = isIndex
        self.relationToIndex = relationToIndex
        self.qualifier = qualifier
        self.diagnoses = diagnoses
        self.mother = mother
        self.father = father
        self.spouses = spouses
        self.siblings = siblings
        self.children = children
    }
}

enum Gender: String, CaseIterable, Identifiable, Codable {
    case male = "Male"
    case female = "Female"
    
    var id: Self { self }
}

enum RelationType: String, CaseIterable, Identifiable, Codable {
    case undefined = "N/A"
    case index = "Index"
    case mother = "Mother"
    case father = "Father"
    case brother = "Brother"
    case sister = "Sister"
    case brotherInLaw = "Brother-in-law"
    case sisterInLaw = "Sister-in-law"
    case son = "Son"
    case daughter = "Daughter"
    case sonInLaw = "Son-in-law"
    case daughterInLaw = "Daughter-in-law"
    case grandson = "Grandson"
    case granddaughter = "Granddaughter"
    case husband = "Husband"
    case wife = "Wife"
    case aunt = "Aunt"
    case uncle = "Uncle"
    case cousin = "Cousin"
    case grandmother = "Grandmother"
    case grandfather = "Grandfather"
    case niece = "Niece"
    case nephew = "Nephew"
    
    var id: Self { self }
}

enum PrimaryRelationType: String, CaseIterable, Identifiable, Codable {
    case parent = "Parent"
    case sibling = "Sibling"
    case spouse = "Spouse"
    case child = "Child"
    
    var id: Self { self }
}

enum RelationQualifier: String, CaseIterable, Identifiable, Codable {
    case none = "None"
    case grand = "Grand"
    case great = "Great"
    case step = "Step"
    case half = "Half"
    case first = "First"
    case second = "Second"
    case third = "Third"
    case fourth = "Fourth"
    
    var id: Self { self }
}

Primary method in question

    func savePerson() {
        let components = Calendar.current.dateComponents([.year, .month, .day], from: dob)
        let newDate = Date.from(year: components.year!, month: components.month!, day: components.day!)
        
        let newRelative = Relative(name: name, gender: gender, dob: newDate, relationToIndex: relation, diagnoses: diagnoses)
        
        
        let relativeToAddTo: Relative
        if self.relativeToAddTo != nil {
            relativeToAddTo = self.relativeToAddTo!
        } else {
            relativeToAddTo = self.index
        }
        
        
        if relativeToAdd == .child {
            if relativeToAddTo.gender == .female {
                newRelative.mother = relativeToAddTo
                //copy father from the last spouse of origin relative
                if let father = relativeToAddTo.spouses.last {
                    newRelative.father = father
                }
            } else {
                newRelative.father = relativeToAddTo
                if let mother = relativeToAddTo.spouses.last {
                    newRelative.mother = mother
                }
            }
            //origin parent's kids are new entry's siblings
            newRelative.siblings += relativeToAddTo.children
            //add reciprocal children/siblings
            for child in relativeToAddTo.children {
                child.siblings.append(newRelative)
            }
            //add this child now to the parent
            relativeToAddTo.children.append(newRelative)
        } else if relativeToAdd == .parent {
            newRelative.children.append(relativeToAddTo)
            if gender == .female {
                relativeToAddTo.mother = newRelative
                //add origin relative's father; we shouldn't need to check if there already is one, because this relative is new, so [spouses] is empty
                if let father = relativeToAddTo.father {
                    newRelative.spouses.append(father)
                    //if the father exists, this new relative is a spouse
                    father.spouses.append(newRelative)
                }
            } else if gender == .male {
                relativeToAddTo.father = newRelative
                //add mother, if she exists
                if let mother = relativeToAddTo.mother {
                    newRelative.spouses.append(mother)
                    //and reciprocate
                    mother.spouses.append(newRelative)
                }
            }
            //origin child's siblings are the new relative's children
            newRelative.children += relativeToAddTo.siblings
        } else if relativeToAdd == .sibling {
            //copy any siblings that already exist
            newRelative.siblings += relativeToAddTo.siblings
            //also add this person to those siblings' lists
            for sibling in relativeToAddTo.siblings {
                sibling.siblings.append(newRelative)
            }
            //then, add the new relative as a sibling, and reciprocal
            newRelative.siblings.append(relativeToAddTo)
            relativeToAddTo.siblings.append(newRelative)
            //siblings can be assumed to share parents
            if let mother = relativeToAddTo.mother {
                newRelative.mother = mother
            }
            if let father = relativeToAddTo.father {
                newRelative.father = father
            }
        } else if relativeToAdd == .spouse {
            newRelative.spouses.append(relativeToAddTo)
            relativeToAddTo.spouses.append(newRelative)
            //nothing further to do
            //spouses can't be assumed to share kids
            //we don't know which marriage each child came from
            //user must manually fix this
        }
        
        modelContext.insert(newRelative)

        do {
            try modelContext.save()
        } catch(let error) {
            print("error: (error.localizedDescription)")
        }
        
        dismiss()
    }

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật