This solution is for a single platform (iOS) with no CloudKit synchronisation: if you need the latter, then I can provide a sample but it’s a bit more complex. I’m not sure how far you’ve got with using Coredata, so I’ve included all that’s necessary to get a solution working.
If you’re creating a new project, in Xcode’s project options select SwiftUI interface, Swift language, but not Use Core Data: if you do, Xcode creates template code that differs from this solution and needs to be gotten rid of.
Create your Xcode data model: in this example, the model name is “BucketListModel” with 2 entities “BucketItem” and “CostItem”. Create a one-to-many relationship between BucketItem and CostItem, with a one-to-one inverse of CostItem to BucketItem. NOTE: These one-to-many and inverse relationships are important in understanding some of the quirks of using CoreData with SwiftUI/Swift.
While still in Xcode’s data model editor, select both entities (BucketItem and CostItem) then in the Inspector panel (right-hand side of screen) change the Codegen option from Class Definition to Manual/None.
Then, still in Xcode’s data model editor, use Editor > Create NSManagedObject Subclass then follow the prompts to select both entities and generate code that goes into your project folder. You should end up with 4 additional Swift files: “CostItem+CoreDataClass”, “CostItem+CoreDataProperties”, “BucketItem+CoreDataClass”, “BucketItem+CoreDataProperties”.
In Xcode, select the BucketItem+CoreDataProperties file and notice that there’s a property called “costItems” which is of type NSSet. SwiftUI cannot use this in a List, so it must be converted into a Swift array. Also, an NSSet is unordered, so it’s best to apply some default sort-order - doing so also creates a Swift array. I created a computed property called costArray, which performs the sort and set conversion. Note the other computed property, totalCost, which performs a reduce operation on the costItems. This is an example of how you add functionality by extending the code in each entitiy’s class.
Create a Swift file (e.g. called “DataModel”) with your (not Xcode's) DataModel class as per the sample below. Key features of this class are: ObservableObject, a singleton (only one instance in the app), loads and manages the Coredata stack, and publishes the bucketList.
import Foundation
import CoreData
class DataModel : ObservableObject {
static let shared = DataModel()
let persistentContainer : NSPersistentContainer
@Published var bucketList = [BucketItem]()
private init() {
self.persistentContainer = {
let container = NSPersistentContainer(name: "BucketListModel")
container.loadPersistentStores(completionHandler: { description, error in
if let error = error {
print("ERROR loading persistent store \(error)")
}
})
return container
}()
}
func saveContext() {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
print("ERROR: Unable to save context")
}
}
}
func addCostItem(bucket: String,name: String,cost: Double) {
let costItem = CostItem(context: persistentContainer.viewContext)
costItem.costName = name
costItem.costAmount = cost
guard let thisBucketItem = getBucketItemFor(bucket) else { return }
thisBucketItem.addToCostItems(costItem)
saveContext()
bucketList = getBucketList()
}
func addBucketItem(name: String,priority: Int, cost: Double, location: String) {
let bucketItem = BucketItem(context: persistentContainer.viewContext)
bucketItem.name = name
bucketItem.priority = Int16(priority)
bucketItem.cost = cost
bucketItem.location = location
bucketItem.date = Date()
saveContext()
bucketList = getBucketList()
}
func getBucketItemFor(_ name: String) -> BucketItem? {
let context = persistentContainer.viewContext
var request = NSFetchRequest<BucketItem>()
request = BucketItem.fetchRequest()
request.fetchLimit = 1
request.entity = NSEntityDescription.entity(forEntityName: "BucketItem", in: context)
request.predicate = NSPredicate(format: "name == %@", name)
do {
let bucketItems = try context.fetch(request)
if bucketItems.count != 1 { return nil}
return bucketItems.first
} catch {
print("ERROR: course fetch failed \(error)")
return nil
}
}
func getBucketList() -> [BucketItem] {
var tempList = [BucketItem]()
let context = persistentContainer.viewContext
let request = NSFetchRequest<BucketItem>()
request.sortDescriptors = [NSSortDescriptor(keyPath: \BucketItem.priority, ascending: true)]
request.entity = NSEntityDescription.entity(forEntityName: "BucketItem", in: context)
do {
if !bucketList.isEmpty { bucketList.removeAll()}
tempList = try context.fetch(request)
} catch {
print("ERROR: fetch failed \(error)")
}
return tempList
}
func getCostTotal() -> Double {
var total = 0.0
let tempList = getBucketList()
total = tempList.reduce(0.0, {$0 + $1.cost})
return total
}
}``