Pre-load core data database in iOS 5 with UIManagedDocument
Asked Answered
M

4

13

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!

Maryn answered 30/12, 2011 at 20:42 Comment(0)
T
9

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.

Thirtyeight answered 14/1, 2012 at 14:33 Comment(0)
B
4

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.

Boatyard answered 29/7, 2012 at 21:30 Comment(1)
What I'm doing is creating the following directory (Documents/AppDatabase/StoreContent) in the documents directory and only coping the persistentStore file in the StoreContent folder. This is working for me. Do you think that I'll have any problem?Meadowlark
M
0

shezi's method seems to work for me, the only thing that I would like to add is "file" means symbolist_en in this case. This may be a bit confusing for new developers like myself (I was putting persistentStore/).

Micronucleus answered 20/11, 2012 at 13:36 Comment(1)
Thanks for pointing that out, I've edited my answer to reflect this.Thirtyeight
G
0

The answer by Shezi helped a lot. But it did not work as is. I dropped the document directory in xcode but it was not getting copied from bundle to Documents directory. Then I stumbled upon macbeb's question regarding pre-load. I tried his approach which involved just including persistentStore file in the bundle. Then creating the required document name directory under Document's folder and then copying persistentStore to that path.

Check the code with the comment "COPY FROM BUNDLE"

I have used static class for document handling.

+(DocumentHandler *) sharedDocumentHandler {
    static DocumentHandler *mySharedDocumentHandler = nil;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        mySharedDocumentHandler = [[self alloc] init];
    });
    return mySharedDocumentHandler;
}

- (id)init {
    self = [super init];
    if (self) {
        NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
        url = [url URLByAppendingPathComponent:@"YourDocumentName"];
        self.sharedDocument = [[MyUIManagedDocument alloc] initWithFileURL:url];
    }
    return self;
}
- (void)performWithDocument:(OnDocumentReady)onDocumentReady
{
    void (^OnDocumentDidLoad)(BOOL) = ^(BOOL success) {
        if(success) {
            onDocumentReady(self.sharedDocument);
        } else {
            NSLog(@"Error occured while creating or opening the database");
        }
    };

    if (![[NSFileManager defaultManager] fileExistsAtPath:[self.sharedDocument.fileURL path]]) {
        NSError *error = nil;

        // COPY FROM BUNDLE

        NSFileManager *fileManager = [NSFileManager defaultManager];

        NSString *DB = [[self.sharedDocument.fileURL path] stringByAppendingPathComponent:@"StoreContent"];

        [fileManager createDirectoryAtPath:DB withIntermediateDirectories:YES attributes:nil error:&error];

        NSLog(@"create directory error: %@",error);

        DB = [DB stringByAppendingPathComponent:@"persistentStore"];

        NSString *shippedDB = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"persistentStore"];

        NSLog(@"%d",[fileManager fileExistsAtPath:shippedDB]);

        [fileManager copyItemAtPath:shippedDB toPath:DB error:&error];

        NSLog(@"Copy error %@",error);        
    }

    if (![[NSFileManager defaultManager] fileExistsAtPath:[self.sharedDocument.fileURL path]]) {
        //Create documents [This is required while creating database first time]
        [self.sharedDocument saveToURL:self.sharedDocument.fileURL
                forSaveOperation:UIDocumentSaveForCreating
               completionHandler:OnDocumentDidLoad];
    } else if (self.sharedDocument.documentState == UIDocumentStateClosed) {
        [self.sharedDocument openWithCompletionHandler:OnDocumentDidLoad];
    } else if (self.sharedDocument.documentState == UIDocumentStateNormal) {
        OnDocumentDidLoad(YES);
    }
}  
Guardrail answered 14/3, 2013 at 4:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.