Present User an error message when SwiftData save fails

Have a data model that sets certain fields as unique. If the user attempts to save a duplicate value, the save fails quietly with no indication to the user that the save failed. The program is on Mac OS 26.0.1

    @Environment(\.modelContext) var modelContext
    @Query private var typeOfContracts: [TypeOfContract]
    @State private var typeName: String = ""
    @State private var typeCode: String = ""
    @State private var typeDescription: String = ""
    @State private var contracts: [Contract] = []
    @State private var errorMessage: String? = "Data Entered"
    @State private var showAlert: Bool = false
    var body: some View {
        Form {
            Text("Enter New Contract Type")
                .font(.largeTitle)
                .foregroundStyle(Color(.green))
                .multilineTextAlignment(.center)
                TextField("Contract Type Name", text: $typeName)
                    .frame(width: 800, height: 40)
                TextField("Contract Type Code", text: $typeCode)
                    .frame(width: 800, height: 40)
                Text("Contract Type Description")
                TextEditor(text: $typeDescription)
                    .frame(width: 800, height: 200)
                    .scrollContentBackground(.hidden)
                    .background(Color.teal)
                    .font(.system(size: 24))
            Button(action: {
                self.saveContractType()
            })
            {
                Text("Save new contract type")
            }
        }
        
    }
    
    func saveContractType() {
        let typeOfContract = TypeOfContract(contracts: [])
        typeOfContract.typeName = typeName
        typeOfContract.typeCode = typeCode
        typeOfContract.typeDescription = typeDescription
        modelContext.insert(typeOfContract)
        do {
            try modelContext.save()
        }catch {
            errorMessage = "Error saving data: \(error.localizedDescription)"
            
        }
    }
}

I have tried to set alerts but Xcode tells me that the alerts are not in scope

Answered by DTS Engineer in 862388022

Thanks for provide the code. I don't see it has anything that should trigger a failure though. If you can elaborate a bit more about what failure you expect to see, I may take another look.

To comment your following description:

When I was saving a duplicate field value, it was not failing, it was replacing the current with a new record, thus it remained unique because the old record was replaced.

This behavior is as-designed, and is known as upsert. For a model that has a unique attribute, when you add a new object and there already exists another object that has the same value for the unique attribute, SwiftData updates the existing object with the new one. No error will be triggered in this case.

For models that don't have any unique attribute, objects are identified by persistentModelID, which is managed by SwiftData. You can add multiple objects that have same values for all the attributes you define, without triggering any conflict.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Your code doesn't include the SwiftData model type (TypeOfContract), and hence isn't runnable. If you can provide a runnable code example, with detailed steps to reproduce the issue, I'd be interested in taking a look.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

import SwiftData
//Model one: type of contract, i.e. Firm Fixed Price, etc
@Model
final class TypeOfContract {
    var contracts: [Contract]
    var typeName: String
    var typeCode: String
    var typeDescription: String
    
    init(contracts: [Contract], typeName: String = "", typeCode: String = "", typeDescription: String = "") {
        self.contracts = contracts
        self.typeName = typeName
        self.typeCode = typeCode
        self.typeDescription = typeDescription
    }
}
//Model two: the Contract
@Model
final class Contract {
    var contractType: TypeOfContract?
    var costReports: [CostReport]
    var contractNumber: String
    var contractName: String
    var startDate: Date
    var endDate: Date
    var contractValue: Double
    var contractCompany: String
    var contractContact: String
    var contactEmail: String
    var contactPhone: String
    var contractNotes: String
    init(contractType: TypeOfContract? = nil, costReports: [CostReport], contractNumber: String = "", contractName: String = "", startDate: Date = .now, endDate: Date = .now, contractValue: Double = 0.0, contractCompany: String = "", contractContact: String = "", contactEmail: String = "", contactPhone: String = "", contractNotes: String = "") {
        self.contractType = contractType
        self.costReports = costReports
        self.contractNumber = contractNumber
        self.contractName = contractName
        self.startDate = startDate
        self.endDate = endDate
        self.contractValue = contractValue
        self.contractCompany = contractCompany
        self.contractContact = contractContact
        self.contactEmail = contactEmail
        self.contactPhone = contactPhone
        self.contractNotes = contractNotes
    }
}
//Model Three: The Cost Reports
@Model
final class CostReport {
    var contract: Contract?
    var periodStartDate: Date
    var periodEndDate: Date
    var bCWP: Double //Budgeted Cost Work Performed
    var aCWP: Double //Actual Cost Work Performed
    var bCWS: Double //Budgeted Cost Work Scheduled
    //Calculated fields
    
    init(contract: Contract? = nil, periodStartDate: Date = .now, periodEndDate: Date = .now, bCWP: Double = 0.0, aCWP: Double = 0.0, bCWS: Double = 0.0) {
        self.contract = contract
        self.periodStartDate = periodStartDate
        self.periodEndDate = periodEndDate
        self.bCWP = bCWP
        self.aCWP = aCWP
        self.bCWS = bCWS
        
    }
}

Here is the model

As a note, I removed the unique modifiers on the two fields because I discovered another issue. When I was saving a duplicate field value, it was not failing, it was replacing the current with a new record, thus it remained unique because the old record was replaced.

Thanks for provide the code. I don't see it has anything that should trigger a failure though. If you can elaborate a bit more about what failure you expect to see, I may take another look.

To comment your following description:

When I was saving a duplicate field value, it was not failing, it was replacing the current with a new record, thus it remained unique because the old record was replaced.

This behavior is as-designed, and is known as upsert. For a model that has a unique attribute, when you add a new object and there already exists another object that has the same value for the unique attribute, SwiftData updates the existing object with the new one. No error will be triggered in this case.

For models that don't have any unique attribute, objects are identified by persistentModelID, which is managed by SwiftData. You can add multiple objects that have same values for all the attributes you define, without triggering any conflict.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

How do I prevent an upsert?

To way to prevent an upsert is to NOT use unique constraints, and instead use your own code to detect duplicates and remind users, if needed.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Do you know of any examples of such code?

I don't, but you can do it be setting up a FetchDescriptor with a predicate to fetch the existing record that that same attribute values, if any. Something like below:

let fetchDescriptor = FetchDescriptor<TypeOfContract>(predicate: #Predicate {
	($0.typeName == yourTargetTypeName) &&
	($0.typeCode == yourTargetTypeCode)
})

if let matchedType = try? yourModelContext.fetch(fetchDescriptor).first {
    ... // Got a duplciate.
} else {
    ... // No duplicate. Insert as a new object.
}

Searching Internet will give you examples as well.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Present User an error message when SwiftData save fails
 
 
Q