import CoreData import CloudKit class CoreDataStack: ObservableObject { static let shared = CoreDataStack() static let appGroupIdentifier = "group.com.yourteam.investmenttracker" static let cloudKitContainerIdentifier = "iCloud.com.yourteam.investmenttracker" lazy var persistentContainer: NSPersistentCloudKitContainer = { let container = NSPersistentCloudKitContainer(name: "InvestmentTracker") // App Group store URL for sharing with widgets guard let storeURL = FileManager.default .containerURL(forSecurityApplicationGroupIdentifier: Self.appGroupIdentifier)? .appendingPathComponent("InvestmentTracker.sqlite") else { fatalError("Unable to get App Group container URL") } let description = NSPersistentStoreDescription(url: storeURL) // CloudKit configuration description.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions( containerIdentifier: Self.cloudKitContainerIdentifier ) // History tracking for sync description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey) description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey) container.persistentStoreDescriptions = [description] container.loadPersistentStores { description, error in if let error = error as NSError? { // In production, handle this error appropriately print("Core Data failed to load: \(error), \(error.userInfo)") } } // Merge policy - remote changes win container.viewContext.automaticallyMergesChangesFromParent = true container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy // Performance optimizations container.viewContext.undoManager = nil container.viewContext.shouldDeleteInaccessibleFaults = true // Listen for remote changes NotificationCenter.default.addObserver( self, selector: #selector(processRemoteChanges), name: .NSPersistentStoreRemoteChange, object: container.persistentStoreCoordinator ) return container }() var viewContext: NSManagedObjectContext { return persistentContainer.viewContext } private init() {} // MARK: - Save Context func save() { let context = viewContext guard context.hasChanges else { return } do { try context.save() } catch { let nsError = error as NSError print("Core Data save error: \(nsError), \(nsError.userInfo)") } } // MARK: - Background Context func newBackgroundContext() -> NSManagedObjectContext { let context = persistentContainer.newBackgroundContext() context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy context.undoManager = nil return context } func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) { persistentContainer.performBackgroundTask(block) } // MARK: - Remote Change Handling @objc private func processRemoteChanges(_ notification: Notification) { // Process remote changes on main context DispatchQueue.main.async { [weak self] in self?.objectWillChange.send() } } // MARK: - Widget Data Refresh func refreshWidgetData() { // Trigger widget timeline refresh when data changes #if os(iOS) if #available(iOS 14.0, *) { import WidgetKit WidgetCenter.shared.reloadAllTimelines() } #endif } } // MARK: - Shared Container for Widgets extension CoreDataStack { static var sharedStoreURL: URL? { return FileManager.default .containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier)? .appendingPathComponent("InvestmentTracker.sqlite") } /// Creates a lightweight Core Data stack for widgets (read-only) static func createWidgetContainer() -> NSPersistentContainer { let container = NSPersistentContainer(name: "InvestmentTracker") guard let storeURL = sharedStoreURL else { fatalError("Unable to get shared store URL") } let description = NSPersistentStoreDescription(url: storeURL) description.isReadOnly = true container.persistentStoreDescriptions = [description] container.loadPersistentStores { _, error in if let error = error { print("Widget Core Data failed: \(error)") } } return container } }