Black screen when attempting to start iCloud app on phone - ios

I have this right now to trigger iCloud loading or not based on whether or not I'm in the simulator. When I try to run on a real device, I get a black screen and the 'addPersistentStore' line seems to hang. "My Project Name" is the name of the entitlements file, and the name of the app.
What's going on?
#if (TARGET_IPHONE_SIMULATOR)
if (![psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:dbUrl
options:nil
error:&error]) {
[NSException raise:#"Open failed" format:#"Reason: %#", [error localizedDescription]];
}
#else
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *ubContainer = [fm URLForUbiquityContainerIdentifier:nil];
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:#"My Project Name" forKey:NSPersistentStoreUbiquitousContentNameKey];
[options setObject:ubContainer forKey:NSPersistentStoreUbiquitousContentURLKey];
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:dbUrl options:options error:&error]) {
[NSException raise:#"Open failed" format:#"%#", [error localizedDescription]];
}
#endif

Apple recommends that when you're using iCloud, you should do all of these steps on a separate thread. Both URLForUbiquityContainerIdentifier and addPersistentStoreWithType:configuration:options:error: will connect to the network, and may block for long periods. The second call-- adding the persistent store-- can block for a lot longer. On iOS, iCloud data is only downloaded on demand, and this demand happens when you add the persistent store. You're getting a blank screen because NSPersistentStoreCoordinator is busy talking to the network (or trying to do so). Apple's sample code puts this on a separate queue, and you should do this too.

Your code doesn't indicate this, but you can't call -URLForUbiquityContainerIdentifier on the main thread. Note from the Apple documentation:
Important: Do not call this method from your app’s main thread.
Because this method might take a nontrivial amount of time to set up
iCloud and return the requested URL, you should always call it from a
secondary thread. To determine if iCloud is available, especially at
launch time, call the ubiquityIdentityToken method instead.
It could very well be that it takes a long time and that it appears as if your app isn't loading, while in reality it is just waiting for that method to return.

Related

Apple rejected - can not reproduce crash

Apple rejected my app with a crash log indicating that they have tested it on an iPhone 6 with iOS 8.0.2. I tested my app with the same iPhone 6 and also on 8.0.2, it works perfect. I tried it with testflight via iTunesConnect, assuming, that this is exactly the same away as Apple does their testing - obviously not!
Does anyone have an idea, how I can solve that issue (already got in contact with the review team, but they couldn't help, as they say they are not technical support and just test the apps.
The problem I have is (from my point of view anyway strange): It crashes when it add the persistent store:
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
/*
Replace this implementation with code to handle the error appropriately. abort() 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 persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
#{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES}
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
It obviously crashes because I have the abort()statement still in there, but I'm not sure what to do instead (i.e to handle the problem - just delete and try to add the store again?)
As it works perfect on my iPhone, I assume, that it is not one of the problems listed in the source code (like directory, model etc. am I right?
I'm struggling with that now for several days, any help or idea is more than appreciated!!
Common reason for this to happen is when there is already a file at storeURL and it does not conform to model that you specify.
As a fix for that here is what you can do
1) Enable lightweight migration
2) Delete existing file and try again
NSDictionary *options = #{NSMigratePersistentStoresAutomaticallyOption: #YES,
NSInferMappingModelAutomaticallyOption: #YES
};
BOOL successOfAdding = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:options
error:&error] != nil;
if (successOfAdding == NO)
{
// Check if the database is there.
// If it is there, it most likely means that model has changed significantly.
if ([[NSFileManager defaultManager] fileExistsAtPath:storeURL.path])
{
// Delete the database
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
// Trying to add a database to the coordinator again
successOfAdding = [_persistentStoreCoordinator addPersistentStoreWithType: NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error] != nil;
if (successOfAdding == NO)
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
And about rejection - do you have previous versions of your app?
Also, where are you attempting to store your database? May be there is no access to that path?

Move local Core Data to iCloud

How can I enable iCloud Core Data in an app which already uses local storage Core Data?
I've tried to use NSPersistentStoreUbiquitousContentNameKey in my persistent store options. Unfortunately, this option enables iCloud but does not transfer any of the local data to iCloud. I can't seem to get migratePersistentStore:toURL:options:withType:error: to work either. I provide the persistent store, its URL, iCloud options, etc. and it still will not migrate the existing local data to iCloud. Here's how I'm using the method:
- (void)migratePersistentStoreWithOptions:(NSDictionary *)options {
NSError *error;
self.storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:[NSString stringWithFormat:#"%#.sqlite", self.SQLiteFileName]];
NSPersistentStore *store = [self.persistentStoreCoordinator migratePersistentStore:self.persistentStoreCoordinator.persistentStores.firstObject toURL:self.storeURL options:options withType:NSSQLiteStoreType error:&error];
if (store) NSLog(#"[CoreData Manager] Store was successfully migrated");
else NSLog(#"[CoreData Manager] Error migrating persistent store: %#", error);
}
The local storage remains separate from the iCloud storage. If possible, I'd like to move the local Core Data to iCloud without manually transferring each entity.
Any ideas? I can find lots of articles, tutorials, and posts about moving back to local storage from iCloud - but I want to move from local storage to iCloud.
Here's what you'll need to do
Create a local NSPersistentStoreCoordinator
Add your existing persistent store to that coordinator and store a reference to this new returned store.
Call that handy migratePersistStore:... providing the store from #2, a URL for the store in the documents directory with a different file name and the all important options including the NSPersistentStoreUbiquitousContentNameKey key.
Here's the code, notes in-line.
NSURL *documentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
//This is the path to the new store. Note it has a different file name
NSURL *storeURL = [documentsDirectory URLByAppendingPathComponent:#"TestRemote.sqlite"];
//This is the path to the existing store
NSURL *seedStoreURL = [documentsDirectory URLByAppendingPathComponent:#"Test.sqlite"];
//You should create a new store here instead of using the one you presumably already have access to
NSPersistentStoreCoordinator *coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
NSError *seedStoreError;
NSDictionary *seedStoreOptions = #{ NSReadOnlyPersistentStoreOption: #YES };
NSPersistentStore *seedStore = [coord addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:seedStoreURL
options:seedStoreOptions
error:&seedStoreError];
NSDictionary *iCloudOptions = #{ NSPersistentStoreUbiquitousContentNameKey: #"MyiCloudStore" };
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//This is using an operation queue because this happens synchronously
[queue addOperationWithBlock:^{
NSError *blockError;
[coord migratePersistentStore:seedStore
toURL:storeURL
options:iCloudOptions
withType:NSSQLiteStoreType
error:&blockError];
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperationWithBlock:^{
// This will be called when the migration is done
}];
}];
Note that after you do this migration, you'll need to configure the persistent store you use with your MOC with the new URL and always include the iCloudOptions above with the NSPersistentStoreUbiquitousContentNameKey key.
This was based on Apple's documentation.
After completion, you should see a new folder in your Documents folder in the simulator folder (~/Library/Application Support/iPhone Simulator/...) labeled CoreDataUbiquitySupport. Nested deep in there is your iCloud synced sqlite store.
Tada!
EDIT: Oh and make sure you have created an iCloud entitlement and included it in your bundle. You should be able to do that all within Xcode, but you can update it on the development portal too.
Take a look at this sample app which includes code to migrate a local core data store to iCloud and back again. Best read the associated docs and build the sample apps in your environment to get them working and once they are working then try and refactor your code to use a similar approach.
Feel free to send me an email for further help. Apologies for not giving you an answer here but it can be quite a complicated issue to deal with.
http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/

iCloud: Sync Core Data between IOS and OSX

I try to sync core data between IOS and OSX. At both apps I have the same configuration:
And the same entitlements:
I also use the same code for the store coordinator within the same name for sqlite file and url:
NSManagedObjectModel* managedModel = [NSManagedObjectModel mergedModelFromBundles:nil];
NSPersistentStoreCoordinator* storeCooordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedModel];
//-> start iCloud
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL* applicationFilesDirectory = [JHDCoreDataDAO applicationFilesDirectory];
NSURL* storeURL = [applicationFilesDirectory URLByAppendingPathComponent:DATABASE_NAME];
if(!storeURL) { NSLog(#"Error reading applicationFilesDirectory for given sqlite resouce"); }
NSString* containerIdentifier = [NSString stringWithFormat:#"%#.%#",TEAM_IDENTIFIER,APP_IDENTIFIER];
NSURL* iCloud = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:containerIdentifier];
NSString *iCloudEnabledAppID = #"TEAMID~com~sample~sample";
NSError* persistentStoreError;
if (iCloud) {
NSLog(#"iCloud is working");
NSLog(#"iCloudEnabledAppID = %#",iCloudEnabledAppID);
NSLog(#"iCloud URL: %#",iCloud);
NSString* cloudPath = [[iCloud path] stringByAppendingPathComponent:#"data"];
NSURL* transactionsLogUrl = [NSURL fileURLWithPath:cloudPath];
NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:
iCloudEnabledAppID, NSPersistentStoreUbiquitousContentNameKey,
transactionsLogUrl, NSPersistentStoreUbiquitousContentURLKey,
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
[storeCooordinator lock];
if(![storeCooordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&persistentStoreError])
{
NSLog(#"Fehler: %#, %#", persistentStoreError.localizedDescription, persistentStoreError.userInfo);
abort();
}
[storeCooordinator unlock];
} else {
//Handle local psc
}
});
Each app, the IOS Version and the OSX Version, are still running perfectly within the iCloud. Each app handle its own database, due to there is no sync between this two apps. Is there somethings what I have forgotton?
You need to be very careful that the identifiers you use in the entitlements match what you use in the source code to access the ubiquity container.
One complication when syncing multiple apps (e.g. Mac app and iOS app) is that you will need to check that they each have the same team identifier. If not, you will want to fill in the team id explicitly for iCloud entitlements, rather than using the TeamIdentifierPrefix variable. Pick one of the team ids and use that for both apps.
In your particular example, it looks like you have entitlements setup for a container id that ends in sample.sample.sample. Assuming the team id is the same for each app, your container id should be XXXXXXXXXX.sample.sample.sample, where the first part is your team id. I don't know what APP_IDENTIFIER is in this instance, but it should be checked to make sure it is sample.sample.sample. (My guess is that it isn't.)
The NSPersistentStoreUbiquitousContentNameKey setting can be basically any label you like for your store. It doesn't have to use the ~ or even be reverse-DNS form. For example, you could call it 'Store1'.
I find the easiest way to see if everything is setup OK is to go to the ~/Library/Mobile Documents folder on your Mac, and look for the app container. You can just browse the contents directly in Finder.
Unfortunately, this is probably the easiest part of getting Core Data working with iCloud. There will be many more hurdles, and they tend to be more challenging. There are new solutions coming up for syncing Core Data all the time, including the TICDS framework and Core Data Ensembles, both of which work with iCloud. (Disclosure: I have contributed to both projects, and founded the Ensembles project.)

Core Data - how to handle _persistentStoreCoordinator errors on first release?

I'm just putting the finishing touches to my app for its first release. I've successfully implemented Core Data but not sure what to do about the persistentStoreCoordinator method that says "Replace this implementation with code to handle the error appropriately."
If I change the model for an update I will look into migration but for now what should I do here?
// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Data.sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() 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 persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
#{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES}
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
The failure of a NSPersistentStore being added to a NSPersistentStoreCoordinator is a developer level error. This should never fail in production if you have done adequate testing. Therefore I always put my own version of the following:
NSLog(#"Failed to load persistent store: %#\n%#", [error localizedDescription], [error userInfo]);
abort(); //My personal version throws a NSException
This is a hard error and should be a hard error 99% of the time. This absolutely should crash in development so that you can know if it happens and you are forced to solve it. Since it is a developer level error you can leave this code in place since it should never happen in production and if it does, you want a crash report sent to Apple.

iPhone core data preset

What is the best way to initialize Core Data database with content. My iPhone app will have a static database with products (data and images). How/where to store images, how to pre-populate database?
Here is what I did:
Create the database inside the iPhone app
I created the model in XCode and do a query against the database (this creates the database)
My static data is a CSV file
Use a Ruby script to read the CSV file
Use the ruby gem sqlite3 to insert data into the database
Copy back into project
Alternative:
Store the CSV/XML file containing data inside the app
Parse it on startup and create your NSMAnagedObjects
Tools/Resources:
Base software for editing/viewing a sqlite3 database
Database Location:
I'm afraid I don't remember it on the top of my head but when you use the simulator your application will be built and copied into a directory. I think the path to your application will be something like this. The database depending on how it is setup is usually in the Documents folder.
~User/Library/Application Settings/iOS Simulator/<version>/<app id>/
Reference the official Apple docs provide a way to prepopulate data into Core Data:
Core Data Books
Within my own application, I replaced the function 'NSPersistentStoreCoordinator' within AppDeledate with this one :
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"CoreDataBooks.CDBStore"];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:[storeURL path]]) {
NSURL *defaultStoreURL = [[NSBundle mainBundle] URLForResource:#"CoreDataBooks" withExtension:#"CDBStore"];
if (defaultStoreURL) {
[fileManager copyItemAtURL:defaultStoreURL toURL:storeURL error:NULL];
}
}
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSError *error;
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() 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 persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
#{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES}
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
Hope this works, good luck!

Resources