Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pre-load core data database in iOS 5 with UIManagedDocument

I'm trying to come up with a way that I can pre-load data into core data while using an UIManagedDocument. My attempt so far is to make the document in a "Loader" app using this code..

NSURL *url  = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
                                                      inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:@"Default Database"];
if(![[NSFileManager defaultManager] fileExistsAtPath:[self.document.fileURL path]]){
    [self.document saveToURL:self.document.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){
        if(success)[self loadDataIntoDocument];
    }];
}

and then copy the persistentStore file from the 'documents' directory that is created in the simulator's directory to the resources section in Xcode for the main app that will be using the loaded database.

My problem is that I can't figure out how to copy that document from the app bundle and use it as a document successfully.

I've tried copying the document directory as-is from the app bundle and trying to access it as a document which gave the error that UIManagedDocument can only access a package of files. I've tried creating another fresh document in the main app and copying the persistentStore from the bundle over the one that is created in the document with the same error. And I've tried using UIManagedDocument's -(BOOL)loadFromContents:ofType:error: method .. I'm not even sure this is what I should be using.

Does anyone have an idea as to how this is normally done? Thanks!

like image 654
Howell21 Avatar asked Dec 30 '11 20:12

Howell21


2 Answers

I don't think you should mess around with the files and directories yourself in iOS.

You can, however, open the document directly from your bundle. For that, you simply have to copy the whole document directory you created in your Loader app into your final bundle. The structure should be rather simple, just two directories with the persistentStore file inside.

Afterwards, when you want to open your document, you need to take care of two things: you need to use the bundle directory as the base directory and you need to add a / at the end of the "file" name, so UIManagedDocument knows that the directory is the document. The code looks like this:

NSURL* url = [[NSBundle mainBundle] bundleURL];
url = [url URLByAppendingPathComponent:@"filename/"];
UIManagedDocument* doc = [[UIManagedDocument alloc] initWithFileURL:url];

Note that you cannot write to that file, as you are not allowed to write inside your application bundle. So if you want to have a writable document, you need to copy that document into your documents folder. You can do that easily by simply saving doc to a different location:

[doc saveToURL:newURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){}]

One more thing: You have to make sure the folder structure is created in your app bundle. The easiest way to make this happen is to drag the whole folder into your project and then select "Create folder references for any added folders". For the "file" symbollist_en (which is actually a bundle/folder itself) it should look like this:
A folder reference in XCode
If you do not do this, the "contents" of the document will reside in your app bundle directly, which cannot be opened as a UIManagedDocument, as you found out.

like image 195
shezi Avatar answered Nov 13 '22 07:11

shezi


The answer by Shezi helped a lot but like others here: Preloaded Core Data Database in ios5 with UIManagedDocument and here: Pre load core data database coming up black with UIManagedDocument I had problems.

Firstly, behaviour on the simulator is noticeably different from behaviour on a device. If you instantiate the UIManagedDocument using initWithURL where the URL points to the app bundle, you'll get a warning that this is a read-only directory in the console when running on the device, but no such warning appears on the simulator. Permission handling seems to be quite different and you can get different results.

The docs suggested that migratePersistentStore:toURL:options:withType:error: should be used instead of [doc saveToURL:newURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success){}]

I spent a long time trying to get this to work but had a lot of trouble getting a pointer to the persistent store. Even though my app worked fine for read-only operations after following Shezi's solution, the persistent store coordinator kept on giving me a null pointer for the persistent store. This happened when I tried both – persistentStores (which returned an empty array) and – persistentStoreForURL: If anyone can explain why this happened, I would be interested. The annoying thing was that it would sometimes work on the simulator, but when I tested on the device, it failed.

In the end, I changed things around and copied the bundle folder into the documents directory first, before instantiating the UIManagedDocument. This seems to have done the trick. Here's the code. It assumes that your UIManagedDocument is a property of the class. I put this in the viewWillAppear method of the initial view controller. Remember that you need to open the document if you instantiate using a URL containing an existing persistent store.

if (!self.yourUIManagedDocument) {
    NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *bundlePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"yourUIManagedDocument"];
    NSString *documentsFolderPath = [documentsDirectory stringByAppendingPathComponent:@"yourUIManagedDocument"];

    NSURL *documentsUrl = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    documentsUrl = [documentsUrl URLByAppendingPathComponent:@"yourUIManagedDocument"];

    if (![[NSFileManager defaultManager] fileExistsAtPath:documentsFolderPath]) {
        NSError *error = nil;

        if([[NSFileManager defaultManager] copyItemAtPath:bundlePath
                                                   toPath:documentsFolderPath
                                                    error:&error]) {
            self.yourUIManagedDocument = [[UIManagedDocument alloc] initWithFileURL:documentsUrl];
            [self.yourUIManagedDocument openWithCompletionHandler:^(BOOL success) {
            }];
        } else {
            NSLog(@"%@", error);
        }
    } else {
        self.yourUIManagedDocument = [[UIManagedDocument alloc] initWithFileURL:documentsUrl];
        [self.yourUIManagedDocument openWithCompletionHandler:^(BOOL success) {
        }];
    }
} else if (self.yourUIManagedDocument.documentState == UIDocumentStateClosed) {
    //Document is closed. Need to open it
    [self.yourUIManagedDocument openWithCompletionHandler:^(BOOL success) {
    }];
}

Of course, the above assumes that you have already generated the database using the simulator and copied the directory into the app bundle in exactly the way Shezi described. Any comments or suggestions for how to improve the code are welcome. I'm still learning here.

like image 44
wrightak Avatar answered Nov 13 '22 08:11

wrightak