From 83390730392875c441d4891e81d613fbe10b54f9 Mon Sep 17 00:00:00 2001 From: Alan Francis Date: Fri, 7 Jul 2023 14:34:03 +0100 Subject: [PATCH] Part 7: Simple PersistenceController --- AppShared/JustOneThingApp.swift | 1 + JustOneThing.xcodeproj/project.pbxproj | 75 ++++++++++++++-- .../contents | 8 ++ Model/PersistenceController.swift | 87 +++++++++++++++++++ WidgetShared/AccessoryWidget.swift | 3 +- WidgetShared/SystemWidget.swift | 1 + 6 files changed, 167 insertions(+), 8 deletions(-) create mode 100644 Model/JustOneThingCloudModel.xcdatamodeld/JustOneThingCloudModel.xcdatamodel/contents create mode 100644 Model/PersistenceController.swift diff --git a/AppShared/JustOneThingApp.swift b/AppShared/JustOneThingApp.swift index 6b820f2..e8d3160 100644 --- a/AppShared/JustOneThingApp.swift +++ b/AppShared/JustOneThingApp.swift @@ -9,6 +9,7 @@ import SwiftUI @main struct JustOneThingApp: App { + let persistenceController = PersistenceController.shared var body: some Scene { WindowGroup { ContentView() diff --git a/JustOneThing.xcodeproj/project.pbxproj b/JustOneThing.xcodeproj/project.pbxproj index aec6168..9e110bc 100644 --- a/JustOneThing.xcodeproj/project.pbxproj +++ b/JustOneThing.xcodeproj/project.pbxproj @@ -39,6 +39,16 @@ 5493A81B2A471E37001EBA08 /* JustOneThingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5493A81A2A471E36001EBA08 /* JustOneThingProvider.swift */; }; 5493A81C2A471E37001EBA08 /* JustOneThingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5493A81A2A471E36001EBA08 /* JustOneThingProvider.swift */; }; 5493A81D2A471E37001EBA08 /* JustOneThingProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5493A81A2A471E36001EBA08 /* JustOneThingProvider.swift */; }; + 54AE28412A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 54AE283F2A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld */; }; + 54AE28422A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 54AE283F2A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld */; }; + 54AE28432A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 54AE283F2A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld */; }; + 54AE28442A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 54AE283F2A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld */; }; + 54AE28452A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 54AE283F2A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld */; }; + 54AE28472A58245700719A56 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AE28462A58245700719A56 /* PersistenceController.swift */; }; + 54AE28482A58245700719A56 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AE28462A58245700719A56 /* PersistenceController.swift */; }; + 54AE28492A58245700719A56 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AE28462A58245700719A56 /* PersistenceController.swift */; }; + 54AE284A2A58245700719A56 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AE28462A58245700719A56 /* PersistenceController.swift */; }; + 54AE284B2A58245700719A56 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54AE28462A58245700719A56 /* PersistenceController.swift */; }; 54BD36092A5700480022D6BE /* ThingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BD36082A5700480022D6BE /* ThingView.swift */; }; 54BD360A2A5700480022D6BE /* ThingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BD36082A5700480022D6BE /* ThingView.swift */; }; 54BD360B2A5700480022D6BE /* ThingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54BD36082A5700480022D6BE /* ThingView.swift */; }; @@ -144,6 +154,8 @@ 54928C462A35F8CB00095445 /* WatchWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WatchWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 54928C4A2A35F8CB00095445 /* SystemWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemWidget.swift; sourceTree = ""; }; 5493A81A2A471E36001EBA08 /* JustOneThingProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustOneThingProvider.swift; sourceTree = ""; }; + 54AE28402A5823D900719A56 /* JustOneThingCloudModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = JustOneThingCloudModel.xcdatamodel; sourceTree = ""; }; + 54AE28462A58245700719A56 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = ""; }; 54BD36082A5700480022D6BE /* ThingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThingView.swift; sourceTree = ""; }; 54BD360C2A57020E0022D6BE /* WidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetBundle.swift; sourceTree = ""; }; 54BD36102A57021E0022D6BE /* AccessoryWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessoryWidget.swift; sourceTree = ""; }; @@ -234,13 +246,9 @@ 5483902E2A2A773900BF5624 = { isa = PBXGroup; children = ( - 5407E5012A2A8A5100EF00B8 /* AppShared */, - 548390392A2A773900BF5624 /* JustOneThing */, - 5431ED6E2A2A77C900515680 /* JustOneThingWatch */, - 5472018B2A3655E4005B2FCC /* WidgetShared */, - 54928C192A35F89900095445 /* MacWidget */, - 54928C312A35F8B800095445 /* PhoneWidget */, - 54928C492A35F8CB00095445 /* WatchWidget */, + 54AE283E2A58230400719A56 /* Model */, + 54AE283C2A5822C600719A56 /* Apps */, + 54AE283D2A5822D400719A56 /* Widgets */, 54928C142A35F89900095445 /* Frameworks */, 548390382A2A773900BF5624 /* Products */, ); @@ -300,6 +308,36 @@ path = WatchWidget; sourceTree = ""; }; + 54AE283C2A5822C600719A56 /* Apps */ = { + isa = PBXGroup; + children = ( + 5407E5012A2A8A5100EF00B8 /* AppShared */, + 548390392A2A773900BF5624 /* JustOneThing */, + 5431ED6E2A2A77C900515680 /* JustOneThingWatch */, + ); + name = Apps; + sourceTree = ""; + }; + 54AE283D2A5822D400719A56 /* Widgets */ = { + isa = PBXGroup; + children = ( + 5472018B2A3655E4005B2FCC /* WidgetShared */, + 54928C192A35F89900095445 /* MacWidget */, + 54928C312A35F8B800095445 /* PhoneWidget */, + 54928C492A35F8CB00095445 /* WatchWidget */, + ); + name = Widgets; + sourceTree = ""; + }; + 54AE283E2A58230400719A56 /* Model */ = { + isa = PBXGroup; + children = ( + 54AE283F2A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld */, + 54AE28462A58245700719A56 /* PersistenceController.swift */, + ); + path = Model; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -493,8 +531,10 @@ buildActionMask = 2147483647; files = ( 5407E5032A2A8AE700EF00B8 /* ContentView.swift in Sources */, + 54AE28422A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld in Sources */, 5407E5022A2A8AE200EF00B8 /* JustOneThingApp.swift in Sources */, 547201932A365868005B2FCC /* Widget.intentdefinition in Sources */, + 54AE28482A58245700719A56 /* PersistenceController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -503,8 +543,10 @@ buildActionMask = 2147483647; files = ( 5483903D2A2A773900BF5624 /* ContentView.swift in Sources */, + 54AE28412A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld in Sources */, 5483903B2A2A773900BF5624 /* JustOneThingApp.swift in Sources */, 54928C222A35F89A00095445 /* Widget.intentdefinition in Sources */, + 54AE28472A58245700719A56 /* PersistenceController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -512,11 +554,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 54AE28492A58245700719A56 /* PersistenceController.swift in Sources */, 54EB3C1F2A5434A30082B059 /* Thing.swift in Sources */, 54928C212A35F89A00095445 /* Widget.intentdefinition in Sources */, 5493A81B2A471E37001EBA08 /* JustOneThingProvider.swift in Sources */, 54BD36092A5700480022D6BE /* ThingView.swift in Sources */, 54BD360D2A57020E0022D6BE /* WidgetBundle.swift in Sources */, + 54AE28432A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld in Sources */, 5416C7382A56A867002196EE /* InterfaceExtensions.swift in Sources */, 547201972A461DBE005B2FCC /* SystemWidget.swift in Sources */, ); @@ -527,6 +571,7 @@ buildActionMask = 2147483647; files = ( 54BD36122A57021E0022D6BE /* AccessoryWidget.swift in Sources */, + 54AE28442A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld in Sources */, 54EB3C202A5434A30082B059 /* Thing.swift in Sources */, 547201962A461DBE005B2FCC /* SystemWidget.swift in Sources */, 5493A81C2A471E37001EBA08 /* JustOneThingProvider.swift in Sources */, @@ -534,6 +579,7 @@ 54BD360E2A57020E0022D6BE /* WidgetBundle.swift in Sources */, 5416C7392A56A867002196EE /* InterfaceExtensions.swift in Sources */, 5472018E2A36563B005B2FCC /* Widget.intentdefinition in Sources */, + 54AE284A2A58245700719A56 /* PersistenceController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -541,11 +587,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 54AE284B2A58245700719A56 /* PersistenceController.swift in Sources */, 54BD36132A57021E0022D6BE /* AccessoryWidget.swift in Sources */, 54EB3C212A5434A30082B059 /* Thing.swift in Sources */, 547201902A36563C005B2FCC /* Widget.intentdefinition in Sources */, 5493A81D2A471E37001EBA08 /* JustOneThingProvider.swift in Sources */, 54BD360B2A5700480022D6BE /* ThingView.swift in Sources */, + 54AE28452A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld in Sources */, 54BD360F2A57020E0022D6BE /* WidgetBundle.swift in Sources */, 5416C73A2A56A867002196EE /* InterfaceExtensions.swift in Sources */, ); @@ -1088,6 +1136,19 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCVersionGroup section */ + 54AE283F2A5823D900719A56 /* JustOneThingCloudModel.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + 54AE28402A5823D900719A56 /* JustOneThingCloudModel.xcdatamodel */, + ); + currentVersion = 54AE28402A5823D900719A56 /* JustOneThingCloudModel.xcdatamodel */; + path = JustOneThingCloudModel.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ }; rootObject = 5483902F2A2A773900BF5624 /* Project object */; } diff --git a/Model/JustOneThingCloudModel.xcdatamodeld/JustOneThingCloudModel.xcdatamodel/contents b/Model/JustOneThingCloudModel.xcdatamodeld/JustOneThingCloudModel.xcdatamodel/contents new file mode 100644 index 0000000..b90f90a --- /dev/null +++ b/Model/JustOneThingCloudModel.xcdatamodeld/JustOneThingCloudModel.xcdatamodel/contents @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Model/PersistenceController.swift b/Model/PersistenceController.swift new file mode 100644 index 0000000..625e179 --- /dev/null +++ b/Model/PersistenceController.swift @@ -0,0 +1,87 @@ +// +// PersistenceController.swift +// JustOneThing +// +// Created by Alan Francis on 07/07/2023. +// + +import CoreData + +//MARK: - URL Extension +// https://developer.apple.com/forums/thread/78120 +// There's an issue with tvOS and the path to the local DB. +// This little extension encapsulates that +public extension URL { + /// Returns a URL for the given app group and database pointing to the sqlite database. + static func storeURL(for appGroup: String, databaseName: String) -> URL { + var containerUrl:URL + + guard let initialContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else { + fatalError("Shared file container could not be created.") + } + + #if os(tvOS) + containerUrl = initialContainer.appendingPathComponent("Library/Caches") + #else + containerUrl = initialContainer + #endif + + return containerUrl.appendingPathComponent("\(databaseName).sqlite") + } +} + +//MARK: - PeristenceController +struct PersistenceController { + let container: NSPersistentCloudKitContainer + + init(inMemory: Bool = false) { + container = NSPersistentCloudKitContainer(name: "JustOneThingCloudModel") + + if inMemory { + container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") + } else { + let url = URL.storeURL(for: "group.com.alancfrancis.apps.JustOneThing", databaseName: "JustOneThingCloudModel") + container.persistentStoreDescriptions.first!.url = url + } + + container.loadPersistentStores(completionHandler: { (storeDescription, error) in + if let error = error as NSError? { + Self.processError(error:error) + } + }) + + container.viewContext.automaticallyMergesChangesFromParent = true + } +} + +//MARK: - static convenience +extension PersistenceController { + static let shared = PersistenceController() + + var viewContext:NSManagedObjectContext { + get { + container.viewContext + } + } +} + +//MARK: - error handling +extension PersistenceController { + static func processError(error:NSError) { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + + /* + Typical reasons for an error here include: + * The parent directory does not exist, cannot be created, or disallows writing. + * The persistent store is not accessible, due to permissions or data protection when the device is locked. + * The device is out of space. + * The store could not be migrated to the current model version. + Check the error message to determine what the actual problem was. + */ + fatalError("Unresolved error \(error), \(error.userInfo)") + } +} + + + diff --git a/WidgetShared/AccessoryWidget.swift b/WidgetShared/AccessoryWidget.swift index f1c0d65..5bf7a52 100644 --- a/WidgetShared/AccessoryWidget.swift +++ b/WidgetShared/AccessoryWidget.swift @@ -11,7 +11,8 @@ import Intents struct JustOneThingAccessoryWidget: Widget { let kind: String = "JustOneThingAccessoryWidget" - + let persistenceController = PersistenceController.shared + var body: some WidgetConfiguration { IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, diff --git a/WidgetShared/SystemWidget.swift b/WidgetShared/SystemWidget.swift index 55a562a..86ffa11 100644 --- a/WidgetShared/SystemWidget.swift +++ b/WidgetShared/SystemWidget.swift @@ -11,6 +11,7 @@ import Intents struct JustOneThingSystemWidget: Widget { let kind: String = "JustOneThingSystemWidget" + let persistenceController = PersistenceController.shared var body: some WidgetConfiguration { IntentConfiguration(kind: kind,