Core data: Failed to load model - swift

I am new to core data.
What I am trying to DO: I am trying to create a cocoatouch framework that has an app to add employee details and display them in a table view. So that i can add this framework to my main project to work independently.
Issues I face: The frame work builds without any error. I have added the core data stack from swift 3 to the framework. But when i run the main project, the moment the framework loads the log displays "Failed to load model named Simple framework", "fetch failed" and "employee must have a valid entity description". The code that I have used in the framework is as shown below :
public class CoreDataStack {
public static let sharedInstance = CoreDataStack()
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "SimpleFramework")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error {
fatalError("Unresolved error \(error), \(error)")
}
})
return container
}()
public func saveContext() {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch let error as NSError {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
}
}
#IBAction func addEmployee(_ sender: Any) {
//To save the data
let context = CoreDataStack.sharedInstance.persistentContainer.viewContext
let employee = Employee(context: context)
employee.employeeName = nameTextField.text
employee.employeeAge = Int16(ageTextField.text!)!
employee.hasVehicle = hasVehicle.isOn
CoreDataStack.sharedInstance.saveContext()
navigationController!.popViewController(animated: true)
}
#IBAction func addEmployee(_ sender: Any) {
//To save the data
let context = CoreDataStack.sharedInstance.persistentContainer.viewContext
let employee = Employee(context: context)
employee.employeeName = nameTextField.text
employee.employeeAge = Int16(ageTextField.text!)!
employee.hasVehicle = hasVehicle.isOn
CoreDataStack.sharedInstance.saveContext()
navigationController!.popViewController(animated: true)
}

I've had this issue, when I had wrong model name - it should be models name, not the projects (see the screen shot)

Explicitly pass the models file name to the Core Data stack for initialization and make sure, it is loaded from the right bundle at the time (test bundle, app bundle...) by using Bundle(for: type(of: self)):
//...
let momdName = "SimpleFramework" //pass this as a parameter
//...
guard let modelURL = Bundle(for: type(of: self)).url(forResource: momdName, withExtension:"momd") else {
fatalError("Error loading model from bundle")
}
guard let mom = NSManagedObjectModel(contentsOf: modelURL) else {
fatalError("Error initializing mom from: \(modelURL)")
}
persistentContainer = NSPersistentContainer(name: momdName, managedObjectModel: mom)
//...
Edit:
Also make sure, the SimpleFramework.xcdatamodeld is added to the used targets Target Membership:

The string you pass to the NSPersistentContainer initializer:
NSPersistentContainer(name: "CoreData")
needs to match the filename of the data model file in your Xcode project:
CoreData.xcdatamodeld

If you want to use CoreData in your dynamic framework you have to subclass NSPersistentContainer and use it instead of NSPersistentContainer.
class PersistentContainer: NSPersistentContainer { }
//...
lazy var container: PersistentContainer = {
let result = PersistentContainer(name: "Your xcdatamodeld file name here")
result.loadPersistentStores { (storeDescription, error) in
if let error = error {
print(error.localizedDescription)
}
}
return result
}()

In my case, for some reason the DataModel.xcdatamodeld became missing from my project workspace.
First I tried creating a new DataModle.xcdatamodeld and recreating the data model, but the same error occurred. Thats when I realized that the Original DataModel.xcdatamodeld was still in the root directory. I fixed this by simply right clicking my project in my project navigator, and selecting "Add files to "Project"...", then I added my old data model and deleted my new data model. Finally I hard cleaned, ran my project and it fixed the issue.

My problem was at my .podspec file. You should include the xcdatamodeld extension on the pod that you are creating.
s.resources = "myprojectfolder/**/*.{png,jpeg,jpg,storyboard,xib,xcassets,xcdatamodeld}"

Related

Creating more than one entity on SwiftUI

I am trying to get more than one entity for my coding project at school but I have an error saying invalid redeclaration of data controller.
class DataController: ObservableObject{
let container = NSPersistentContainer(name: "Blood Sugar")
init() {
container.loadPersistentStores { description, error in
if let error = error {
print("Core Data failed to load: \(error.localizedDescription)")
}
}
}
}
class DataController : ObservableObject{
let containers = NSPersistentContainer(name: "Carbohydrates")
init(){
containers.loadPersistentStores{ description, errors in
if let errors = errors{
print("Core data failed to load: \(errors.localizedDescription)")
}
}
}
}
Since you tagged this as SwiftUI, DataController should be a struct. We use value types like structs now to solve a lot of the bugs caused by using objects in UIKit and ObjC. You can see Apple's doc Choosing Between Structures and Classes for more info.
If you use an Xcode app template project and check "Use core data" you'll see a PersistenceController struct that will demonstrate how to do it correctly. I've included it below:
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 {
// 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: "SearchTest")
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
}
}
You can create more entities in the model editor. There is usually only one NSPersistentContainer per app. The container can have multiple stores (usually sqlite databases). Then you can assign different entities to each store too. To create an instance of an entity you do that on a NSManagedObjectContext and you can choose which store to save it too, although most of the time people use one store which is the default.

Getting nil managedObjectContext in CoreData with Swift working from a Library

I'm developing a library for iOS using Swift 5, and I want this library to use CoreData independent of the application which consumes that library and this is what I've done so far:
Created the entities with their respective data types
Created the .xcdatamodeld file, which contains the entities
Created a CoreDataManager which looks like this:
// MARK: - CoreDataManager
final class CoreDataManager {
static let shared = CoreDataManager()
private static let defaultObject = NSManagedObject.init()
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Audit")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
}
And the trouble is I'm trying to get the context by:
let coreDataManager = CoreDataManager.shared
let context = coreDataManager.persistentContainer.viewContext
and context is returning nil
please help
I solved it, and apparently the trouble was that the ios application where I wanted to use my library wasn't finding the .xcdatamodeld file which resulted in a useless NSPersistentContainer object, which also meant that let context = persistentContainer.viewContext was nil.
In order to avoid this kind of troubles in the future, I'll left a list of important considerations when working with CoreData and swift libraries.
Key things to consider
Make sure the app that is consuming your library knows exactly where
to look for it. Might want to take a look at this article for details.
If you are working with cocoapods for distributing your library, make sure to add the following to your .podspec:
s.resources = "path/to/model.xcdatamodeld"
This will produce a folder named "Resources" in your Pods target:
Make sure your model file name matches the NSPersistentContainer name.
(NOT SURE ABOUT THIS) I changed the class definition of my NSManagedObjects from
class Audit: NSManagedObject {}
to
public class Audit: NSManagedObject {}
And even when I'm not sure if that makes sense, It could work for you.
Finally I'll leave the code that worked for me
// MARK: - CoreDataManager
final class CoreDataManager {
static let shared = CoreDataManager()
private static let defaultObject = NSManagedObject.init()
lazy var persistentContainer: NSPersistentContainer? = {
let modelURL = Bundle(for: Audit.self).url(forResource: "Audit", withExtension: "momd")
guard let model = modelURL.flatMap(NSManagedObjectModel.init) else {
print("Fail to load the trigger model!")
return nil
}
let container = NSPersistentContainer(name: "Audit", managedObjectModel: model)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
}
And to get the context outside
let coreDataManager = CoreDataManager.shared
guard
let context = coreDataManager.persistentContainer?.viewContext
else {
print("Nil context case")
return
}
Hope you guys find it helpful!!

Swift - how to pass managed object context to different view controllers

I put the core data stack in its own file as shown below. I read that using dependency injection is the best way to pass the managed object context. So in each of a handful of VCs, I declare the following property:
var managedObjectContext: NSManagedObjectContext?
Now, the tricky part is getting the moc from my stack to the different VCs. Which seems like a great place for a singleton, but assuming that's a bad idea, I guess I would use the code below in CoreDataStack:
let controller = self.window!.rootViewController as! ViewController
let context = self.persistentContainer.viewContext
controller.managedObjectContext = context
But that leaves me with a few questions:
1) Where in CoreDataStack should I include the code above? In the App Delegate it would go in didFinishLaunchingWithOptions, but that's not really an option now.
2) Writing the above code for every single vc that needs a context seems bad. I guess I could loop through all the VCs. I've seen the moc passed using didSet too, but that doesn't seem quite right either.
CoreData Stack
class CoreDataStack {
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Model")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}

When saving to CoreData from Today Extension, Data only accesable from Widget - NOT from Main Application

I'm writing an Application for iOS with Swift 3.
In my Main iOS App I'm using an NSFetchedResultsController to show saved items as an TableView.
It's (of course) possible to add new items from another ViewController.
-> Thats all working really awesome.
So I thought it would be great if I could add an new item really fast from an TodayWidget.
What I did:
Created an SharedCode Framework and added AppGroup to my Main App and the Today Widget.
Moved my CoreDataStack.swift Class, the .xcdatamodeled and my Item+CoreDataClass.swift and Item+CoreDataProperties.swift files to my SharedCode Framework.
Sublcassed NSPersistentContainer to addforSecurityApplicationGroupIdentifier for my appGroupID
Rewrote my CoreData code in my ViewController to use the created CoreDataStack.shared.managedContext
Testing. AWESOME. My NSFetchedResultsController is working, and adding new Items works as expected. Nice. -> moveing on.
In my Today Widget I'm using an simple NSFetchRequest to get the last entered Item from CoreData. Works perfectly!
Added Buttons to modify the data and saving it to CoreData. Here I'm also using CoreDataStack.shared.managedContext and CoreDataStack.shared.save()
Automatically reloading my Data AND?! AWESOME. All working very nice. Data is saved and new data is shown in the Today Extension. Also when I count the results from my NSFetchRequest the number of Items is increased.
NOW TO MY PROBLEM:
All the data that I'm adding through the Extension is not showing in my main iOS App.
There when I'm fetching the Items from CoreData there are only the ones showing that I created in the main app. -.-
I have no Idea whats going wrong.
It's like I have two different CoreDataStores.
Also after I once added an Item trough the Widget - the Widget does not fetch an Item from the main App. Only the last one entered from the Widget.
- Here is my Code:
CoreDataStack.swift
public class CoreDataStack {
public static let shared = CoreDataStack()
public var errorHandler: (Error) -> Void = {_ in }
//#1
lazy var persistentContainer: PersistentContainer = {
let container = PersistentContainer(name: ServiceConsts.modelName)
var persistentStoreDescriptions: NSPersistentStoreDescription
let storeUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: ServiceConsts.appGroupID)!.appendingPathComponent("\(ServiceConsts.modelName).sqlite")
let description = NSPersistentStoreDescription()
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
description.url = storeUrl
container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: ServiceConsts.appGroupID)!.appendingPathComponent("\(ServiceConsts.modelName).sqlite"))]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
//#2
public lazy var managedContext: NSManagedObjectContext = {
return self.persistentContainer.viewContext
}()
//#3
// Optional
public lazy var backgroundContext: NSManagedObjectContext = {
return self.persistentContainer.newBackgroundContext()
}()
//#4
public func performForegroundTask(_ block: #escaping (NSManagedObjectContext) -> Void) {
self.managedContext.perform {
block(self.managedContext)
}
}
//#5
public func performBackgroundTask(_ block: #escaping (NSManagedObjectContext) -> Void) {
self.persistentContainer.performBackgroundTask(block)
}
//#6
public func saveContext () {
guard managedContext.hasChanges else { return }
do {
try managedContext.save()
} catch let error as NSError {
print("Unresolved error \(error), \(error.userInfo)")
}
}
}
PersistentContainer.swift
class PersistentContainer: NSPersistentContainer {
internal override class func defaultDirectoryURL() -> URL {
var url = super.defaultDirectoryURL()
if let newURL =
FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: ServiceConsts.appGroupID) {
url = newURL
}
return url
}
}
Can anyone help?
I have really no idea what I'm doing wrong.
Would be so awesome if anyone can give me a tip :)
Thanks <3
I finally fixed my issue <3.
It was so simple.
After hours and hours of testing testing crying and putting an axed through my MacBook ;) I found the thing thats what close killing me.
I testet if a simple Fetch Request in the Main App would get the added Items from the Today Extension. And that was working.
And than I saw it. In my Main App.
The NSFetchedResultController. I used an cache.
So now I notify the Main App when a new Weight was added via the widget - and than I call my refresh function.
func handleRefresh(_ refreshControl: UIRefreshControl) {
NSFetchedResultsController<NSFetchRequestResult>.deleteCache(withName: "weightCache")
do {
try fetchedResultsController.performFetch()
setupView()
self.tableView.reloadData()
} catch let error as NSError {
print("Fetching error: \(error), \(error.userInfo)")
}
refreshControl.endRefreshing()
}
So simple.
Just delete the cache.

Core Data with pre-filled .sqlite (Swift3)

currently I'm working on a Swift3 / iOS10 update of an existing iOS9 App which stores about 10.000 charging points for electric vehicles across europe. Up to now I always shipped the Application with a pre-filled database (.sqlite, .sqlite-shm, .sqlite-wal files from the .xcappdata bundle), but with the current Version Apple is introducing the NSPersistentContainer Class, which makes it a bit more complicated. In my AppDelegate Class I'm instantiating my NSPersistentContainer object and passing it to a lazy var like it's done by Apple in every example code:
lazy var stationDataPersistentContainer: NSPersistentContainer = {
let fileMgr = FileManager.default
let destinationModel = NSPersistentContainer.defaultDirectoryURL()
if !fileMgr.fileExists(atPath: destinationModel.appendingPathComponent("StationData.sqlite").path) {
do {
try fileMgr.copyItem(at: URL(fileURLWithPath: Bundle.main.resourcePath!.appending("/StationData.sqlite")), to: destinationModel.appendingPathComponent("/StationData.sqlite"))
try fileMgr.copyItem(at: URL(fileURLWithPath: Bundle.main.resourcePath!.appending("/StationData.sqlite-shm")), to: destinationModel.appendingPathComponent("/StationData.sqlite-shm"))
try fileMgr.copyItem(at: URL(fileURLWithPath: Bundle.main.resourcePath!.appending("/StationData.sqlite-wal")), to: destinationModel.appendingPathComponent("/StationData.sqlite-wal"))
} catch {
//
}
} else {
//
}
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "StationData")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
/*
* 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)")
}
})
return container
}()
In the iOS9 version im copying the files to the apropriate directory, like you can see in the following code example:
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("StationData.sqlite")
let fileMgr = NSFileManager.defaultManager()
if !fileMgr.fileExistsAtPath(url.path!) {
do {
try fileMgr.copyItemAtPath(NSBundle.mainBundle().pathForResource("StationData", ofType: "sqlite")!, toPath: self.applicationDocumentsDirectory.URLByAppment("StationData.sqlite").path!)
try fileMgr.copyItemAtPath(NSBundle.mainBundle().pathForResource("StationData", ofType: "sqlite-shm")!, toPath: self.applicationDocumentsDirectory.URLByAppendingPathComponent("StationData.sqlite-shm").path!)
try fileMgr.copyItemAtPath(NSBundle.mainBundle().pathForResource("StationData", ofType: "sqlite-wal")!, toPath: self.applicationDocumentsDirectory.URLByAppendingPathComponent("StationData.sqlite-wal").path!)
} catch {
//
} do {
try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url,
options: [NSMigratePersistentStoresAutomaticallyOption:true, NSInferMappingModelAutomaticallyOption:true])
} catch {
//
}
} else {
//
}
return coordinator
}()
For a number of days I have tried to move the files to the proper directory which is returned by NSPersistentContainer.defaultDirectoryURL() -> URL, but everytime I get an error, that the file already exists because my stationDataPersistentContainer is already initialized and so the NSPersistentContainer had enough time to generate the sqlite* files. Even if I try to copy the files and initialize the stationDataPersistentContainer in an overwritten init() function I could not get this right. Is there anything I'm missing or overlooking in the documentation? Which is the best/right/appropriate way to copy existing data on installation of an App into coredata.
Appendix:
Just for your Information, I could also store the JSON-Files, which I get from my API into the Documents directory and run the JSON-parser, but this needs a lot of ressources and especially time! (This question is also posted on the Apple Developers Forum and waiting for approval)
This is how I do it:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "app_name")
let seededData: String = "app_name"
var persistentStoreDescriptions: NSPersistentStoreDescription
let storeUrl = self.applicationDocumentsDirectory.appendingPathComponent("app_name.sqlite")
if !FileManager.default.fileExists(atPath: (storeUrl.path)) {
let seededDataUrl = Bundle.main.url(forResource: seededData, withExtension: "sqlite")
try! FileManager.default.copyItem(at: seededDataUrl!, to: storeUrl)
}
print(storeUrl)
container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: storeUrl)]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error {
fatalError("Unresolved error \(error),")
}
})
return container
}()
The simplest solution is to not use NSPersistentContainer - it's only a convenience for taking away the stack boilerplate, and if you're updating an already working app, you could just leave your code as it is.
If you want to migrate to NSPersistentContainer, then move the files before you have called loadPersistentStores - it's at that point that the SQL files are first created.
Note that NSPersistentContainer may not be using the documents directory - out of the box on the iOS simulator it uses the application support directory. If using the documents directory is important to you, then you have to subclass NSPersistentContainer and override the defaultDirectoryURL class method, or provide an NSPersistentStoreDescription to the NSPersistentContainer which tells it where to store the data.