Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing Core Data SQL Database in iOS 8 Extension (Sharing Data Between App and Widget Extension)

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
like image 506
Mark Bridges Avatar asked Jul 08 '14 21:07

Mark Bridges


4 Answers

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] 
like image 138
Mark Bridges Avatar answered Oct 05 '22 23:10

Mark Bridges


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

like image 25
kas-kad Avatar answered Oct 05 '22 22:10

kas-kad


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];
}
like image 27
Igor Avatar answered Oct 05 '22 23:10

Igor


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

like image 40
Bartłomiej Semańczyk Avatar answered Oct 06 '22 00:10

Bartłomiej Semańczyk