34

Problem:

Unable to access application's Core Data database from within a Widget Extension in the Today View.

The app itself is able to read and write to the database as per normal under iOS 8, but the extension will fail to create the store, giving the error, unable to write to file.

The log is the following:

Error Domain=NSCocoaErrorDomain Code=512 "The operation couldn’t be completed. (Cocoa error 512.)"

reason = "Failed to create file; code = 2
Bartłomiej Semańczyk
  • 59,234
  • 49
  • 233
  • 358
Mark Bridges
  • 8,228
  • 4
  • 50
  • 65

4 Answers4

68

Widgets are unable to access the NSDocuments directory, which is where one would normally store their database.

The solution is to first create an App Group

Go to: Project - Target - App Groups - Add New Container

Name the container, i.e. 'group.mycontainer'

Repeat the process for the Widget's Target using the same name for the container.

Then write your database to your group container.

So:

NSURL *storeURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory  inDomains:NSAllDomainsMask] lastObject];
storeURL = [storeURL URLByAppendingPathComponent:@"db.sqlite"];

Becomes:

NSURL *storeURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.mycontainer"];
storeURL = [storeURL URLByAppendingPathComponent:@"db.sqlite"];

And initialising the store should be like so:

NSURL *storeURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.mycontainer"];
storeURL = [storeURL URLByAppendingPathComponent:@"db.sqlite"];

NSPersistentStore *store = nil;
store = [coordinator addPersistentStoreWithType:NSSQLiteStoreType
                                  configuration:nil
                                            URL:storeURL
                                        options:nil
                                          error:&error]
Mark Bridges
  • 8,228
  • 4
  • 50
  • 65
  • 2
    But how do I get the coordinator? Do I need the model (momd) for this? If yes, how can I share the model with the extension? – Ben Aug 21 '14 at 09:04
  • 1
    @BenjaminHerzog You share the momd file just add it to the extension target as well as the container app target. – mokagio Aug 24 '14 at 05:48
  • I'm a bit confuse how do I get this to work in the app? I was able to use the NSFileManager this way in the extension but not the main app. It returns a nil unlike the extension. – Phong Le Aug 27 '14 at 00:05
  • What would happen if the containing app decides to delete and recreate the persistent store while the extension is using it? – Awesome-o Sep 19 '14 at 18:55
  • I can get this to work in the Simulator, but not an an actual device. Is there something I am missing? – Choppin Broccoli Sep 20 '14 at 22:32
  • ^ It only works for me on the device by running from the debugger. Add hock builds crash immediately. – Awesome-o Sep 22 '14 at 23:14
  • Nvm, works like a charm. Just have to set up the entitlements correctly for all Schemes :) – Awesome-o Sep 23 '14 at 05:53
  • 7
    @Mark how would data migration scenario work? Imagine pre ios 8 the app stores data at the original storeURL, now a new version has to first crate the new file in the new storeURL and then somehow migrate the original data into the new store. – Sean Dong Oct 06 '14 at 21:54
  • @Mark this looks and works fine but how will be the locking work when the extension and the app both will try and read/write on the same physical file. – Ravi Dalmia Mar 20 '15 at 11:19
  • I'm using new Xcode `App Groups` is located in your Project > Select your app target > Capabilities (Capabilities is next to General and Info, Build Phases etc). – Zhang Mar 25 '15 at 05:24
  • This doesn't work when using iCloud (add NSPersistentStoreUbiquitousContentNameKey in options). It works in simulator because iCloud is by default off in simulator. Is there anyway to store data that have iCloud on? – hebinda Jul 07 '15 at 04:32
7

Just figured out that the app group files do not get backed up using the standard iOS backup procedure.

Keep in mind that the user may lose all their app data after restoring iOS if you keep the persistent store in an app group container.

UPDATE

rdar://18750178

UPDATE

seems like fixed in iOS 8.1, Apple guys messaged me and asked to check the problem in iOS 8.1 whether it fixed or not (quite impudent isn't?). I haven't tested it, so keep in mind. Anyway, keeping storage in AppGroups is a dead idea in case you are supporting defective iOS 8.0

kas-kad
  • 3,736
  • 1
  • 26
  • 45
5

Change

[MagicalRecord setupCoreDataStackWithStoreNamed:@"Database"];

to

 - (void)setupCoreDataStack
{
     if ([NSPersistentStoreCoordinator MR_defaultStoreCoordinator] != nil)
     {
        return;
    }

    NSManagedObjectModel *model = [NSManagedObjectModel MR_defaultManagedObjectModel];
    NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];

    NSURL *storeURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.yourgroup"];
    storeURL = [storeURL URLByAppendingPathComponent:@"Database.sqlite"];

    [psc MR_addSqliteStoreNamed:storeURL withOptions:nil];
    [NSPersistentStoreCoordinator MR_setDefaultStoreCoordinator:psc];
    [NSManagedObjectContext MR_initializeDefaultContextWithCoordinator:psc];
}
Igor
  • 4,235
  • 3
  • 34
  • 32
1

The same for Swift:

private func setupCoreDataStack() {

    if NSPersistentStoreCoordinator.MR_defaultStoreCoordinator() != nil {
        return
    }

    let managedObjectModel = NSManagedObjectModel.MR_defaultManagedObjectModel()
    let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
    var storePath = NSFileManager.defaultManager().containerURLForSecurityApplicationGroupIdentifier(PBOSharedSuiteGroupName)
    storePath = storePath!.URLByAppendingPathComponent("AppName.sqlite")

    var error: NSError?
    persistentStoreCoordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storePath, options: nil, error: &error)
    NSPersistentStoreCoordinator.MR_setDefaultStoreCoordinator(persistentStoreCoordinator)
    NSManagedObjectContext.MR_initializeDefaultContextWithCoordinator(persistentStoreCoordinator)
}

Remember to attach this method to both: AppDelegate and Today Extension

Bartłomiej Semańczyk
  • 59,234
  • 49
  • 233
  • 358