37

I'm trying to migrate a specific part of one of my apps into a framework so that I can use it in my app itself and in one of those fancy new iOS 8 widgets. This part is the one that handles all my data in Core Data. It's pretty straight forward to move everything over and to access it. I'm just having trouble accessing my momd file in there.

When creating the NSManagedObjectModel I still try to load the momd as illustrated in Apple's code templates:

NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"MyApp" withExtension:@"momd"];
__managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

Unfortunately, modelURL stays nil and thus MyApp crashes when accessing the Core Data stack with this error:

2014-08-01 22:39:56.885 MyApp[81375:7417914] Cannot create an NSPersistentStoreCoordinator with a nil model
2014-08-01 22:39:56.903 MyApp[81375:7417914] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Cannot create an NSPersistentStoreCoordinator with a nil model'

So, what's the right way to do this when working inside a framework with Core Data?

flohei
  • 5,248
  • 10
  • 36
  • 61
  • Is the file itself included in the target you're building? Looks to me like the file you're referencing isn't getting added in as part of the target build. – Bill Patterson Aug 01 '14 at 20:53
  • My `MyApp.xcdatamodeld` is included in the memberships of my framework target, yes. For the `momd` file itself I have no idea if I can change that anywhere. Isn't that one created during build/on the fly based on my `xcdatamodeld` file? – flohei Aug 01 '14 at 21:51
  • I've never included a model file as part of a framework. When I need to include a model, I build the model from source code. However, I imagine your problem is that you are looking in the main bundle. Have you tried iterating through all bundles, trying to load the model from each one until you get success?, using `[NSBundle allBundles]`? – Jody Hagins Aug 03 '14 at 00:17
  • Yes, I tried that. That did not work for me. I iterated over all bundles trying to find "all" `MyApp.momd`. The array where I would've stored all the URLs was empty after running through all my bundles. – flohei Aug 03 '14 at 05:58
  • @flohei That does not sound right. However, there's another option, instead of packaging your actual model file. Once you are set on your model, load it, archive it to a `NSData` object, then turn that into a base64 encoded string. You can write a simple program to do it, or log it to the console and cut/paste the string. You can then set a variable to be that base64 encoded string. When your code runs, you can then decode the string into `NSData` and then unarchive it into a `NSManagedObjectModel` instance. Your model is now part of your compiled library. – Jody Hagins Aug 05 '14 at 20:36

7 Answers7

42

I'm a bit late for flohei's issue, but hopefully this helps anybody else who wanders by. It is possible to get this to work without having to perform script-fu to copy resources around!

By default Apple's Core Data template gives you something like this:

lazy var managedObjectModel: NSManagedObjectModel = {
  let modelURL = NSBundle.mainBundle().URLForResource("MyCoreDataModel", withExtension: "momd")!
  return NSManagedObjectModel(contentsOfURL: modelURL)!
}()

That would load your Core Data resources out of the main bundle. The key thing here is: if the Core Data model is loaded in a framework, the .momd file is in that framework's bundle. So instead we just do this:

lazy var managedObjectModel: NSManagedObjectModel = {
    let frameworkBundleIdentifier = "com.myorg.myframework"
    let customKitBundle = NSBundle(identifier: frameworkBundleIdentifier)!
    let modelURL = customKitBundle.URLForResource("MyCoreDataModel", withExtension: "momd")!
    return NSManagedObjectModel(contentsOfURL: modelURL)!
}()

That should get you up and running.

Credit: https://www.andrewcbancroft.com/2015/08/25/sharing-a-core-data-model-with-a-swift-framework/

Jonathan Zhan
  • 1,883
  • 1
  • 14
  • 17
13

You need to drag the xcdatamodeld file and drop it in the Build Phases | Compile Sources for the targets that use the framework. Then when the target runs its [NSBundle mainBundle] will contain the model (momd file).

user3771857
  • 617
  • 5
  • 3
7

@Ric Santos was almost there. I think you just need to make it an "mom" extension rather than "momd", then it will run.

lazy var managedObjectModel: NSManagedObjectModel = {
    let modelURL = NSBundle(forClass: self.dynamicType.self).URLForResource(self.dataModelName, withExtension: "mom")!
    return NSManagedObjectModel(contentsOfURL: modelURL)!
}()
Faqinghere
  • 101
  • 1
  • 6
  • 1
    Had the same problem, your solution worked! Thanks alot! – Jan Nov 16 '15 at 14:08
  • Changing from NSBundle.mainBundle() to NSBundle(forClass: self.dynamicType.self) was what worked for me. Didn't need to change to "mom" though. Why is dynamicType used and why doesn't it work without it? :) – Rygen Aug 24 '16 at 09:39
4

I may be a little late with answering this, but here's what solved my issue:

With my framework I'm delivering also a bundle with resources like images and other stuff. Putting xcdatamodeld file there didn't give anything as this file being built with the project and as a result you get a momd folder in your app bundle (which actually is missing in our case).. I have created another target, not framework, but app, built it and copied the momd from its app bundle to my separate bundle in the project (the one that goes with framework). After doing this you just need to change your resource url from main bundle to the new one:

// ...
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"separate_bundle" ofType:@"bundle"];
NSURL *modelURL = [[NSBundle bundleWithPath:bundlePath] URLForResource:@"your_model" withExtension:@"momd"];
// ...

Worked fine for me. The only thing I'm aware of is App Store Review which I didn't get to yet. So if you've found a better solution, please share.

EDIT

Found better solution. You can build the model yourself. From Core Data Programming Guide:

A data model is a deployment resource. In addition to details of the entities and properties in the model, a model you create in Xcode contains information about the diagram—its layout, colors of elements, and so on. This latter information is not needed at runtime. The model file is compiled using the model compiler, momc, to remove the extraneous information and make runtime loading of the resource as efficient as possible. An xcdatamodeld “source” directory is compiled into a momd deployment directory, and an xcdatamodel “source” file is compiled into a mom deployment file.

momc is located in /Developer/usr/bin/. If you want to use it in your own build scripts, its usage is momc source destination, where source is the path of the Core Data model to compile and destination is the path of the output.

By "/Developer/usr/bin/" they mean "/Applications/Xcode.app/Developer/usr/bin/"

You can add a script in you target scheme and compile this automatically before each build (or after, don't think it matters). This is in case if you change the model during development.

Something like this:

mkdir -p "${BUILT_PRODUCTS_DIR}/your_model_name.momd"
momc "${SRCROOT}/your_model_path/your_model_name.xcdatamodeld" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/your_bundle_name.bundle/your_model_name.momd"
jackal
  • 1,143
  • 17
  • 32
  • I used the script you mentioned above , but can't still access core data model in the framework, why ? – Weizhi Jul 24 '15 at 10:03
  • @dee, please check carefully the conditional names I've used (like **your_model**, **your_model_name** or **separate_bundle**, etc) after pasting the code. Perhaps you have missed to change one of those with the name specific to your project? I actively use this code in my projects and it works fine for me. Let me know you have questions. – jackal Jul 25 '15 at 22:34
  • I did change conditional names. But I found `mkdir -p "${BUILT_PRODUCTS_DIR}/your_model_name.momd" ` will caused error while build the framework. I have to change it to `momc "${SRCROOT}/RichAPM/Source/DB/RichAPM.xcdatamodeld" "${BUILT_PRODUCTS_DIR}/your_framework_name.framework/your_bundle_name.bundle/model_name.momd"` Even I build the framework and bundle successfully , I still can't use the model. Will you please give me a hand? – Weizhi Jul 26 '15 at 03:08
  • I finally got it works, but I have to import the framework and bundle separately. – Weizhi Jul 26 '15 at 03:19
  • @dee, you need to add the script I posted in the Run Script section in your target's build phases. – jackal Jul 27 '15 at 07:20
2

The model resource is no longer accessible via the mainBundle, you need to use bundleForClass: like so:

NSURL *modelURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"MyApp" withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
Ric Santos
  • 15,419
  • 6
  • 50
  • 75
0

I assume you only require the data model.

If so, I find the following is consistently the most successful method for copying across a data model file from one project to another...

  1. Delete any current copies of .xcdatamodeld file that reside in the target project.

  2. Close Xcode.

    Using Finder (or cmd line);

  3. Select your Xcode project folder that contains the original .xcdatamodeld file.

  4. Make a copy of the original .xcdatamodeld file.

  5. Move the copy of the original .xcdatamodeld file to your target project.

    Then...

  6. Open Xcode.

  7. Using the Menu command "Add Files to >your project<", find and add the copy of the original .xcdatamodeld file to your project.

  8. Rename the original .xcdatamodeld file (if necessary) using Project Navigator.

  9. "Build & Run" your target project.

Does this help?

Community
  • 1
  • 1
andrewbuilder
  • 3,629
  • 2
  • 24
  • 46
  • No, unfortunately this does not help. I think that moving the file worked just fine, anyway. It's just that at runtime the path can't be constructed because (I assume) that the `momd` still resides inside the original bundle (or wherever) and is just not accessible from within the framework. But that's just a wild guess. – flohei Aug 02 '14 at 05:57
0

If you try to have core data in your framework do like this.

in YourFramework - add new file / core data / Data model ... - create all object ....

When creating new project that will use YourFramework be sure that core data is on Use Core Data : On

This will create all boiler plate inside AppDelegate.

in Test project - add Framework - add Framework as embedded framework - DELETE .xcdatamodeld file - in AppDelegate :

change - (NSManagedObjectModel *)managedObjectModel method into :

NSBundle * testBundle = [NSBundle bundleWithIdentifier:@"YourFramework bundle id "];

NSURL *modelURL = [testBundle URLForResource:@"Model" withExtension:@"momd"];

where YourFramework bundle id is Bundle Identifier of YourFramework (in General / Bundle Identifier)

and Model is name of your .xcdatamodeld file in YourFramework

This works.

Hope it helps.

Marko Zadravec
  • 8,298
  • 10
  • 55
  • 97