Use CoreDate with swift

Hello,

is it possible to make all the CoreData stuff in a vanilla swift file? My problem is: I want to do all the CoreData stuff of an SwiftUI project in a DataManager (not a SwiftUI View), which is a distinguished swift file, how can I do this? (Maybe give some example code) Thx

Did you solve this? If not, I have sample code from a recent project, which also includes CloudKit synchronisation and multi-platform.

Regarda, Michaela

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
    }
}``


PART 2 of answer (had to split because of character limit)

EXAMPLE of Coredata Class Properties generated by Xcode and added to by developer (me)

import Foundation
import CoreData
extension BucketItem {
    @nonobjc public class func fetchRequest() -> NSFetchRequest<BucketItem> {
        return NSFetchRequest<BucketItem>(entityName: "BucketItem")
    }
    @NSManaged public var cost: Double
    @NSManaged public var date: Date?
    @NSManaged public var location: String?
    @NSManaged public var name: String?
    @NSManaged public var priority: Int16
    @NSManaged public var costItems: NSSet?
}

// MARK: Generated accessors for costItems
extension BucketItem {
    @objc(addCostItemsObject:)
    @NSManaged public func addToCostItems(_ value: CostItem)
    @objc(removeCostItemsObject:)
    @NSManaged public func removeFromCostItems(_ value: CostItem)
    @objc(addCostItems:)
    @NSManaged public func addToCostItems(_ values: NSSet)
    @objc(removeCostItems:)
    @NSManaged public func removeFromCostItems(_ values: NSSet)

    // MARK: Developer Generated properties for costItems MUST be replaced if Xcode regenerates other code
    public var costArray : [CostItem] {
        if costItems == nil { return [CostItem]() }
        return costItems?.sorted(by: {($0 as! CostItem).costAmount < ($1 as! CostItem).costAmount}) as! [CostItem]
    }

    public var totalCost : Double {
        if costItems  == nil { return 0.0 }
        return costItems?.reduce(0.0, {$0 + ($1 as! CostItem).costAmount}) ?? 0.0
    }
}

extension BucketItem : Identifiable {
}

There are 3 ways of accessing the DataModel (and CoreData stack) within SwiftUI Views:

With a @StateObject (e.g. in the main App view) definition and subsequent environment method

@StateObject var persistenceController = DataModel.shared
.....
ContentView()
                .environment(\.managedObjectContext, persistenceController.persistentContainer.viewContext)

Reference to the data model as an ObservedObject.

@ObservedObject var dataModel = DataModel.shared
...
List(dataModel.getBucketList(), id:\.name) { item in

Direct reference to DataModel properties and methods 

DataModel.shared.addCostItem(bucket: "RTW Trip", name: "Lots", cost: 40000.0)

Which to use depends on the need and the volatility/mutability of the data.

I hope this helps.  Regards, Michaela

Use CoreDate with swift
 
 
Q