I have an already published app that use core data.
Now I want to add support for watch kit and today extension.
I need to move core data into shared container without lose previous user saved data, how can I do that in the best way?
You can migrate a Core Data Stack. A fuller answer can be found here, but the short version is:
Check if the old non-group copy of the data exists
If it does, set up a Core Data stack using that file. Then use migratePersistentStore:toURL:options:withType:error: to move it to the new location. Then remove the old copy.
If the old version doesn't exist, just set up Core Data with the new copy as usual.
(The problem with Stephen's answer is that it assumes that the Core Data stack is a single SQLite file, which isn't always true.)
Here is how I moved core data to the shared container in my app. I do this when the app launches.
NSUserDefaults* sharedDefs = [GPMapCore sharedCore].sharedUserDefaults;
if (![sharedDefs boolForKey:#"CoreDataMovedToExtension"])
{
NSURL* oldLocation = GET_LOCATION_OF_CORE_DATA_SQLITE_FILE();
NSURL* newLocation = GET_LOCATON_TO_MOVE_THE_SQLITE_FILE_TO();
if ([[NSFileManager defaultManager] fileExistsAtPath:[oldLocation filePathString]])
{
//Check if a new file exists. This can happen when the watch app is run before
//Topo Maps+ runs and move the core data database
if ([[NSFileManager defaultManager] fileExistsAtPath:[newLocation filePathString]])
{
[[NSFileManager defaultManager ] removeItemAtURL:newLocation error:nil];
}
[[NSFileManager defaultManager] moveItemAtURL:oldLocation toURL:newLocation error:nil];
}
[sharedDefs setBool:YES forKey:#"CoreDataMovedToExtension"];
[sharedDefs synchronize];
}
Related
I have a feature in my app whereby the User can reset everything on the app by click of a button. At this point, instead of trying to delete all the Core Data relations (Cascade Delete) and other complications, I decided to actually remove the entire UIManagedDocument using this piece of code
-(void)cleanUpDocument
{
[[NSFileManager defaultManager] removeItemAtPath:[self.document.fileURL path] error:nil];
}
This should remove the Document I assume? But it sometimes throws an error. And the weird part is that, when I try to re-create the Document the next time, I get an error saying "Can't create File, File already Exists". The code that i use to create the Document is this :-
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.document.fileURL path]]) {
[self.document saveToURL:self.document.fileURL
forSaveOperation:UIDocumentSaveForCreating
completionHandler:nil]
}
My question is this :- what is the best/correct way to remove/delete an entire UIManagedDocument and start fresh on next successful login?
Thanks in advance.
I just had the same issue and tried exactly your approach at first, only to be greeted by similar errors. From what I gather, it's not the best (or at least not necessary) to delete the entire UIManagedDocument, but rather only the underlying persistent store (while keeping this managedObjectContext in sync, of course).
This answer worked for me: https://stackoverflow.com/a/8467628/671915
The problem is that you're removing the file while some objects still hold a reference to it and are keeping it open.
The correct solution is to do this:
[document closeWithCompletionHandler:^(BOOL success){
if([[NSFileManager defaultManager] fileExistsAtPath:[document.fileURL path]]){
[[NSFileManager defaultManager] removeItemAtURL:document.fileURL error:nil];
}
just wondering if someone else came across this.
I got this piece of code that used to work brilliant in previous xcode versions.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:#"mydb.sqlite"];
/*
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:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"mydb" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
[self addSkipBackupAttributeToItemAtURL:storeUrl];
NSError *error;
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. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
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.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return persistentStoreCoordinator;
}
I would expect the following from this:
an empty "mydb" to be created from scratch if there is no "mydb.sqlite" in my bundle.
if a "mydb.sqlite" exists in my main bundle then i would expect it to be copied in the specified diretory.
if the "mydb.sqlite" is not compatible with my xcdatamodel the app must crash.
however this works only with already created db's previously.
If for example i try to put a random db named "mydb.sqlite" to my bundle and delete the original one then,
the app doesnt crash!!! a blank db is created and the new db is ignored.
This is completely wrong as it goes against my code.
In addition if I add back the original db nothing happens and the app just creates a blank db.
(and yes I do clean my project, delete the sim app, and even delete the build folder before any change occurs!)
any ideas??
There is a difference between your code and your expectation as you've stated it.
Here is your expectation:
an empty "mydb" to be created from scratch if there is no "mydb.sqlite" in my bundle.
if a "mydb.sqlite" exists in my main bundle then i would expect it to be copied in the specified directory.
if the "mydb.sqlite" is not compatible with my xcdatamodel the app must crash.
Here is what your code does:
Look for mydb.sqlite in the Documents directory
If mydb.sqlite does not exist, copy it from the main bundle (if it exists in the main bundle)
Create a new persistent store at ~/Documents/mydb.sqlite
The main problem I think you're experiencing is because of Step 3. addPersistentStoreWithType:... creates a new store at the URL provided. You instead need to requery the existence of the file (to check that there is now a file existing which may have been copied from the MainBundle) and if it exists, then use [persistentStoreCoordinator persistentStoreForURL:storeURL] instead of creating a new persistent store.
Once you have fixed this, the other problems should be more easily traceable. I suggest adding many more NSLog's in to your code, so you can see whether the code is following the execution path you expect. E.g. log each new object you create so you can easily see if any of them are nil. When you copy your bundle db, add an NSError pointer rather than NULL, and then if the error is non-nil, log it.
the answer to the question/ observation is simple.
downgrade to 4.2.1
I have restored my xcode to 4.2.1 (thank God for Time Machine) and now ALL is as expected.
I will file a bug report to Apple later on today.
Check that your default db is actually being included in the bundle - i.e. it is checked as included in the project, and is in the copy files build phase. I rearranged a project and an SQLite file, whilst bring copied into the project structure, was not included in my project - this would cause the behaviour you are seeing.
I am already pre-populating data for my application by first creating the database through core data, then populating that initialized file with SQLite Manager. Is it possible to pre-populate images in a SQLite table for use in core data as well?
My initial thought is to insert the images as a blob through SQLite Manager. Then based on this post, it looks like I would need to set the type to binary and import with UIImage initWithData:.
Is this doable, and if so, is this the appropriate method?
Pre-populating images to a SQLite database for use with Core Data turns out to be fairly trivial.
First configure your Core Data application, implementing your attribute to contain images as a "Binary" type. Build the application and navigate to your view utilizing Core Data within the application simulator. This will initialize the SQLite database as required for use with Core Data (assuming you've implemeted the persistentStoreCoordinator as follows).
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"YourDBName.sqlite"];
// 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:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"YourDBName" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
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]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
return persistentStoreCoordinator;
}
Navigate to the application data at "Users//Library/Application Support/iPhone Simulator/User/Applications/". If you sort the folder by "Date Modified," your application will have the latest date (assuming you haven't built any other applications in the mean time). Enter the application folder and the initialized < YourDBName.sqlite > will reside in the Documents folder. Copy the SQLite database to another location (like your desktop) and delete the original file (this is necessary to allow Core Data to reload the pre-populated SQLite database you are about to create).
Open < YourDBName.sqlite > with your favorite SQLite editor (the SQLite Manager plugin for Firefox is an adequate, and free, option). Add entries to your table, inserting any images as a "BLOB."
Within XCode add < YourDBName.sqlite > as an existing file. Core Data will copy this file to the application data folder the next time you launch your application if it does not already exist there (you deleted the original right?).
Access your pre-populated images within your code with [UIImage imageWithData:< DataObject >.< ImageAttributeName >
How big are your images going to be? If they are fairly large you may be better served by storing the image in the file system and keeping a reference to its location in core data.
If the images will always exist in your app then you can package them with your bundle. If not (e.g. a user can remove unwanted images) you may have to rely on pulling the images in on first use.
I'm trying to use a pre-generated sqlite file containing 10 000 objects in a table.
I've created and added objects, with iPhone simulator, in the sqlite using coredata.
I've copy and past the sqlite contained in iPhone Simulator ressource folder (containing 10 000 objects), into my ressource folder in my project directory.
What i do at first launch of my app, is copy this generated database into my app document directory on the iphone using :
NSBundle * mainBundle = [NSBundle mainBundle];
NSString *oldPath = [mainBundle pathForResource:#"MyBase" ofType:#"sqlite"];
NSString *newPath = [[app_delegate applicationDocumentsDirectory] stringByAppendingPathComponent: #"MyBase.sqlite"];
BOOL copied = [[NSFileManager defaultManager] copyItemAtPath:oldPath toPath:newPath error:&error];
if (!copied) {
NSLog(#"Moving database from %# to %#, error: %#", oldPath, newPath, error);
}
It works fine, but i have the following problem :
Comparing access to the original MyBase.sqlite (created on my device and filled with the same 10 000 objects) with the new copy, all access on tables take 3 times more time than on the normal generated MyBase.sqlite.
I wonder if when generating sqlite on simulator, indexed attribute does not exist?
I need help!
Your using a fairly common technique and it does not normally cause any issues. Core Data cannot tell the difference between a store just created and an old one if both stores use the same data model.
The only explanation I can think of is that you are using two different system/API versions such that the store file is subtly different. If the version on device is older/newer than the version on simulator you might have problems.
That's just a wild guess.
I have found several snippets of code describing how to write data to a user's application Documents folder. However, when I try this out in the iPhone simulator, no files get created. I called
[NSFileManager isWritbleAtPath:<my document folder>]
and it returned 0 (false). Do I need to make this folder explicitly writable, and if so, how do I do it?
The iPhone simulator should be able to write to the entire disk. My app routinely dumps test files to the root level of my boot volume (using [NSData's writeToPath:#"/test.jpg" atomically:NO]).
Are you sure that you've correctly determined the path to the documents folder? You need to expand the tilde in the path. Here's the code my app uses to put things in the documents folder. I don't think there's any more setup involved!
brushesDir = [[#"~/Documents/BrushPacks/" stringByExpandingTildeInPath] retain];
// create brush packs folder if it does not exist
if (![[NSFileManager defaultManager] fileExistsAtPath: brushesDir])
[[NSFileManager defaultManager] createDirectoryAtPath:brushesDir withIntermediateDirectories:YES attributes:nil error:nil];
NSLog(#"writable: %d", [[NSFileManager defaultManager] isWritableFileAtPath:NSHomeDirectory()]);
This prints 1 on the console.
Did you mean to call the method isWritableAtPath or isWritableFileAtPath ? And did you mean to call it on the class itself, or on a (default) instance of it?
Thanks for the pointers. So after a toiling through a few documents, I found the thing I was doing wrong: trying to save an NSArray that wasn't composed of basic datatypes such as NSDictionary, NSArray, or NSString. I was trying to save an array of MPMediaItems (from the MediaKit Framework in SDK 3.0+).
I had a trivial issue with the file writing to NSBundle. I had a requirement where a text file needs to be updated with the server as soon as app launches and it worked well with the simulator but not with the device. I later found out that we don't have write permission with NSBundle. Copying the file into Documents directory from NSBundle and using for my purpose solved my problem. I use :
[myPlistData writeToFile:fileName atomically:NO];