Swift CoreData -> Cloudkit Using Public Database - BAD REQUEST - swift

I have a CoreData private database that I'd like to make into a public database. I've done this before but this database is more complicated and perhaps that is the underlying issue. When I check the CloudKit console logs all I see i a request flagged as BAD REQUEST. How can I tell why it is a bad request?
I am not getting any data synched to CloudKit.
My setup in PersistenceController is ...
import CoreData
import CloudKit
import UIKit
struct PersistenceController
{
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
return result
}()
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false)
{
container = NSPersistentCloudKitContainer(name: "SBWorkbook")
if inMemory
{
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
guard let description = container.persistentStoreDescriptions.first else
{
fatalError()
}
description.cloudKitContainerOptions?.databaseScope = .public
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.transactionAuthor = "Me"
container.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
container.loadPersistentStores(completionHandler:
{ (storeDescription, error) in
if let error = error as NSError?
{
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}

Related

how to preview core data in a PreviewProvider

This is in my View
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let persistenceController = PersistenceController.shared
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
.previewDevice("iphone 14 pro")
}
}
This is in my persistence file
import CoreData
class PersistenceController: ObservableObject {
static let shared = PersistenceController()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
for _ in 0..<10 {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
let TodoItem = Todo(context: viewContext)
TodoItem.name = "clean"
TodoItem.priority = "High"
}
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: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "coreDataToDo")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
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
}
}
I cant figure out how to load the data that is stored in core data and not that data that I randomly generated in PersistenceController in the for loop. What I am getting in the preview is mostly what is in the forloop

Core Data and CloudKit sync is inconsistent

I have an app that uses Core Data and CloudKit using the public database. The problem is that deletes never seem to sync and additions and changes don't show up until the app enters the background and then returns to the foreground, and even those results are inconsistent.
The example app is just the default app you get when specifying SwiftUI and Core Data. I modified the Schema in the CloudKit dashboard to add the two indexes recordName and modifiedAt.
The following is the Persistence.swift file, so to reproduce, create a new project, chose Core Data and Use CloudKit, Add Capability for CloudKit and Remote Notifications, then replace the Persistence.swift contents with the above.
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
for _ in 0..<10 {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
}
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "TestCKSink")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
guard let description = container.persistentStoreDescriptions.first else
{
fatalError()
}
//description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
description.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
description.cloudKitContainerOptions?.databaseScope = .public
container.viewContext.transactionAuthor = "Me"
container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}
}
I have tried this with and without the history tracking option turned on. As I said, this works some of the time. I also realize that I need to add code to force refresh the display, but even checking the CloudKit dashboard and querying the records, some do not don't show there at all or take a long time to arrive (18 minutes).
Any suggestions would be welcomed.
I experienced similar issues some time ago, try the following setting when initializing your managed object context:
managedObjectContext.automaticallyMergesChangesFromParent = true

What does this Error in SwiftUI Core Data WatchOS mean?

i have an iOS App which deals with Core Data.
Now I want to create a watchOS App which has some of the iOS Views.
My Goal is to let the user create a CoreData Object on the Watch.
If I select in the Target Membership of the CoreData File the watch extension and create a FetchRequest for my Objects everything works fine until I open that view.
If I open the view I get the following error code:
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
I have read that there is some kind of solution for UserDefaults but I haven't found anything for CoreData.
Does anyone know how to fix this and how to fetch the Core Data properly?
Edit:
CoreData File: Generated via XCode12
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "Fitness")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}
}
Fetch Request in Watch Extension
#Environment(\.managedObjectContext) var viewContext
#FetchRequest(
entity: Item.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Item.name, ascending: false)]
) var Itemlist: FetchedResults<Item>
I shared all relevant files with the watch extension.

Unit test core data with multiple entities

I would like to test my core data methode.
There is multiples entities in my coredataModel and for each I have a NSManagedObject class
there is methode inside those classes to add, delete and remove data of the corresponding entity.
public class StoredGame: NSManagedObject {
static private let storage = DataManager.shared.storage
static var all: [Game] {
let request: NSFetchRequest<StoredGame> = StoredGame.fetchRequest()
guard let storedGame = try? storage.viewContext.fetch(request) else { return [] }
var games: [Game] = .init()
storedGame.forEach { (storedGame) in
games.append(convert(storedGame))
}
return games
}
static func add(new game: Game) {
let entity = NSEntityDescription.entity(forEntityName: "StoredGame", in: storage.viewContext)!
let newGame = StoredGame(entity: entity, insertInto: storage.viewContext)
try? storage.saveContext()
}
}
and then I have a class responsible of the core data stack
class CoreDataManager {
private lazy var persistentContainer: NSPersistentContainer! = {
guard let modelURL = Bundle.main.url(forResource: "CoreData", withExtension:"momd") else {
fatalError("Error loading model from bundle")
}
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
guard let mom = NSManagedObjectModel(contentsOf: modelURL) else {
fatalError("Error initializing mom from: \(modelURL)")
}
let container = NSPersistentContainer(name: "CoreData", managedObjectModel: mom)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
var viewContext: NSManagedObjectContext {
return persistentContainer.viewContext
}
func saveContext () throws {
let context = viewContext
if context.hasChanges {
do {
try context.save()
} catch(let error) {
print(error)
}
}
}
}
Then when it goes to the tests. I've created a mockContainer and a mockCoreData
lazy var mockContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "CoreData")
container.persistentStoreDescriptions[0].url = URL(fileURLWithPath: "/dev/null")
container.loadPersistentStores(completionHandler: { (_, error) in
XCTAssertNil(error)
})
return container
}()
lazy var mockCoreData = {
return StoredGame(context: mockContainer.viewContext)
}()
So now I dont know how to run tests in that configuration, I've tried a
XCTAssert(StoredGame.all.isEmpty) for exemple ( i have a all var in the StoredEntity class)
but it fails with an error telling
'NSInvalidArgumentException', reason: '-[CoreData.StoredEntity setId:]: unrecognized selector sent to instance
any idea?
This might be occurring with passing an invalid URL for the store description. Unless you need to run tests with a NSSQLiteStoreType, which is the default for NSPersistentContainer, you may want to consider using an NSInMemoryStoreType for unit testing. A small tweak to your CoreDataManager class could allow you to initialize the class both for your app and unit tests. For example:
class CoreDataManager {
private let persisted: Bool
init(persisted: Bool = true) {
self.persisted = persisted
}
lazy var persistentContainer: NSPersistentContainer = {
let description = NSPersistentStoreDescription()
if persisted {
description.type = NSSQLiteStoreType
description.url = // location to store the db.
} else {
description.type = NSInMemoryStoreType
}
let container = NSPersistentContainer(name: "CoreData")
container.persistentStoreDescriptions = [description]
container.loadPersistentStores //...
return container
}()
}
Then you can use this exact class in your unit tests without need to create a mock. Just initialize it without persistence:
let manager = CoreDataManager(persisted: false)

Where Core Data is saving?

I use this code to initialize Core Data:
import Cocoa
import CoreData
class DataController: NSObject {
var persistentContainer: NSPersistentContainer!
var context: NSManagedObjectContext!
override init() {
persistentContainer = NSPersistentContainer(name: "Highlightings")
persistentContainer.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
context = persistentContainer.viewContext
}
}
How can I specify a .sqlite file where Core Data should save data?
You could go back to the older approach without NSPersistentContainer. The old API has not been deprecated.
If you're using NSPersistentContainer, you can change the store location using NSPersistentStoreDescription. Something like
let container = NSPersistentContainer(name: "ContinerName")
let storeURL = // Initialize to whatever URL you want
let description = NSPersistentStoreDescription(url: storeURL)
container.persistentStoreDescriptions = [ description ]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
// ...
}