Skip to content

Commit

Permalink
Enable CloudKit
Browse files Browse the repository at this point in the history
  • Loading branch information
daneden committed Jul 5, 2024
1 parent 0eb9b82 commit a819ab7
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 64 deletions.
145 changes: 94 additions & 51 deletions Data Model/Persistence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,63 +7,106 @@

import CoreData

struct PersistenceController {
static let shared = PersistenceController()

static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
for i in 0..<10 {
let newItem = SavedLocation(context: viewContext)
newItem.title = "Example location \(i)"
}
do {
try viewContext.save()
} catch {
// 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.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()

let container: NSPersistentCloudKitContainer
class PersistenceController {
static let shared = PersistenceController()


init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "Solstice")

if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
} else {
let fileUrl = NSPersistentContainer.defaultDirectoryURL().relativePath + "/Solstice.sqlite"
container.persistentStoreDescriptions.first?.url = URL(filePath: fileUrl)
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
for entry in SavedLocation.defaultData {
let newItem = SavedLocation(context: viewContext)
newItem.title = entry.title
newItem.subtitle = entry.subtitle
newItem.latitude = entry.latitude
newItem.longitude = entry.longitude
newItem.timeZoneIdentifier = entry.timeZoneIdentifier
newItem.uuid = entry.uuid
}
do {
try viewContext.save()
} catch {
// 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.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()

let container: NSPersistentCloudKitContainer

init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "Solstice")

if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
} else {
let fileUrl = NSPersistentContainer.defaultDirectoryURL().relativePath + "/Solstice.sqlite"
container.persistentStoreDescriptions.first?.url = URL(filePath: fileUrl)
}

container.persistentStoreDescriptions.first?.cloudKitContainerOptions = .init(containerIdentifier: "iCloud.me.daneden.Solstice")

container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as 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)")
}
})

// Observe Core Data remote change notifications.
NotificationCenter.default.addObserver(self,
selector: #selector(storeRemoteChange(_:)),
name: .NSPersistentStoreRemoteChange,
object: container.persistentStoreCoordinator)

container.viewContext.automaticallyMergesChangesFromParent = true
}

@objc func storeRemoteChange(_ notification: Notification) {
deduplicateRecords()
}

func deduplicateRecords() {
container.performBackgroundTask { context in
for entry in SavedLocation.defaultData {
do {
guard let uuidString = entry.uuid?.uuidString else { return }
let request = SavedLocation.fetchRequest()
request.predicate = NSPredicate(format: "uuid == %@", uuidString)
let results = try context.fetch(request)

for (index, result) in results.enumerated() {
if index != 0 {
context.delete(result)
}
}

try context.save()
} catch {
print(error.localizedDescription)
}

container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as 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)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
}
}
}

extension SavedLocation {
public override func awakeFromInsert() {
super.awakeFromInsert()
uuid = UUID()

if uuid == nil {
uuid = UUID()
}
}
}
19 changes: 19 additions & 0 deletions Data Model/SavedLocation++.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,22 @@ extension SavedLocation {
uuid: uuid)
}
}

extension SavedLocation {
static let nycUUIDString = "7AAA4D87-4402-4D0E-A35E-2D84641A71BE"

static var defaultData: [SavedLocation.CodableRepresentation] {
guard let defaultDataUrl = Bundle.main.url(forResource: "defaultData", withExtension: "json") else {
print("No URL for defaultData.json")
return []
}

do {
let defaultDataFileData = try Data(contentsOf: defaultDataUrl)
return try JSONDecoder().decode([SavedLocation.CodableRepresentation].self, from: defaultDataFileData)
} catch {
print(error.localizedDescription)
return []
}
}
}
14 changes: 3 additions & 11 deletions Solstice/Helpers/AppChangeMigrator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,12 @@ struct AppChangeMigrator: ViewModifier {
}
}
.task(id: "Default data initialisation/migration") {
let nycUUIDString = "7AAA4D87-4402-4D0E-A35E-2D84641A71BE"
guard let defaultDataUrl = Bundle.main.url(forResource: "defaultData", withExtension: "json") else {
return print("No URL for defaultData.json")
}

do {
let defaultDataFileData = try Data(contentsOf: defaultDataUrl)
let defaultData = try JSONDecoder().decode([SavedLocation.CodableRepresentation].self, from: defaultDataFileData)

let fetchRequest = SavedLocation.fetchRequest()
let currentData = try modelContext.fetch(fetchRequest)

if currentData.isEmpty {
for entry in defaultData {
for entry in SavedLocation.defaultData {
let newRecord = SavedLocation(context: modelContext)
newRecord.title = entry.title
newRecord.subtitle = entry.subtitle
Expand All @@ -46,8 +38,8 @@ struct AppChangeMigrator: ViewModifier {
modelContext.insert(newRecord)
try modelContext.save()
}
} else if let newYorkStateEntry = currentData.first(where: { $0.uuid?.uuidString == nycUUIDString }),
let newYorkCityEntry = defaultData.first(where: { $0.uuid?.uuidString == nycUUIDString }){
} else if let newYorkStateEntry = currentData.first(where: { $0.uuid?.uuidString == SavedLocation.nycUUIDString }),
let newYorkCityEntry = SavedLocation.defaultData.first(where: { $0.uuid?.uuidString == SavedLocation.nycUUIDString }){
newYorkStateEntry.title = newYorkCityEntry.title
newYorkStateEntry.subtitle = newYorkCityEntry.subtitle
newYorkStateEntry.latitude = newYorkCityEntry.latitude
Expand Down
3 changes: 1 addition & 2 deletions Solstice/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@
<true/>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>location</string>
<string>processing</string>
<string>remote-notification</string>
</array>
</dict>
</plist>
8 changes: 8 additions & 0 deletions Solstice/Solstice.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@
<array>
<string>solstice.daneden.me</string>
</array>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.me.daneden.Solstice</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
Expand Down
1 change: 1 addition & 0 deletions watchOS/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<key>UIBackgroundModes</key>
<array>
<string>location</string>
<string>remote-notification</string>
</array>
</dict>
</plist>
10 changes: 10 additions & 0 deletions watchOS/Solstice.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.me.daneden.Solstice</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>
</array>
<key>com.apple.security.application-groups</key>
<array>
<string>group.me.daneden.Solstice</string>
Expand Down

0 comments on commit a819ab7

Please sign in to comment.