5

After some research I am still unsure on when / how I should create my UIManagedDocument.

@interface ViewController ()
@property (strong, nonatomic) UIManagedDocument *document;
@end

@implementation ViewController

- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];

NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsDirectory = [[fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
NSString *documentName = @"MyDocument";
NSURL *url = [documentsDirectory URLByAppendingPathComponent:documentName];

BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[url path]];

if (fileExists) {
    [self.document openWithCompletionHandler:^(BOOL success) {
        NSLog(@"Exsits dont create again");
        if (success) [self documentIsReadyCreateAndObject];
        if (!success) NSLog(@"cound not open document at %@", url);
    }];
}
else {
    // create it
    [self.document saveToURL:url forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
        NSLog(@"Create it");
        if (success) [self documentIsReadyCreateAndObject];
        if (!success) NSLog(@"cound not create document at %@", url);
    }];
}
}

- (void) documentIsReadyCreateAndObject
{
if (self.document.documentState == UIDocumentStateNormal) {
    NSLog(@"document is ready - create obj");
    Car *demo = [NSEntityDescription insertNewObjectForEntityForName:@"Car" inManagedObjectContext:self.document.managedObjectContext];
    demo.carName = @"xxxxx";
}
else {
    NSLog(@"Not ready to go");
}
}

- (void) fetchAndPrint
{
if (self.document.documentState == UIDocumentStateNormal) {
    NSLog(@"document is ready - print objs");
    NSFetchRequest *request = [[NSFetchRequest alloc]initWithEntityName:@"Car"];
    NSArray *results = [self.document.managedObjectContext executeFetchRequest:request error:nil];

    for (Car *aLog in results) {
        NSLog(@"\nNAME: %@", aLog.carName);
    }
}
else {
    NSLog(@"Not ready to go");
}
}

@end
DogCoffee
  • 19,820
  • 10
  • 87
  • 120

2 Answers2

4

View Controller vs. App Delegate

The best time to create your UIManagedDocument depends a little on the situation, but I find that in most cases the app's delegate is the best place for it. A view controller is really not the place to create a database. Most of the time, you'll be using that database in multiple places in your app, so it wouldn't really be the task of one specific view controller to create it. If the design of your app changes such that that view controller is not the first controller on screen, you have to take all that code and put it in some other view controller.

Since a Core Data backed model usually needs to be accessed from many different view controllers, I use the app delegate for that. As soon as your app is launched, it will create or open a database that view controllers can use pretty much immediately.

That doesn't mean that you have to access your document from the app delegate as well. There's a lot of debate about whether that's good practice or not, but I prefer to pass the document to whatever object needs so that it doesn't depend on the app delegate for its model.

Most of the time, the only reason you pass a UIManagedDocument is for its NSManagedObjectContext, so you could consider just passing that if you don't need any other document-related properties. Also, if you just pass one NSManagedObject, you automatically have access to its context, so that could reduce the number of times you have to pass your document.

Migration

I'll answer your last question first: no, using the traditional Core Data stack or UIManagedDocument does not matter with regards to migration (lightweight or using mapping models). UIManagedDocument has a persistentStoreOptions property that you can use to handle most migrations for you. Even if you need a mapping model, you can create that, add it to your bundle and if you made the right mappings, it will do it all for you.

Scott Berrevoets
  • 16,921
  • 6
  • 59
  • 80
  • thanks. The app crashes when putting it in the app delegate, hence the main reason for the question. The view controller viewDidLoad wants to access the MOC, (but i assume as the set up is in a block, can occur will a delay), so viewDidLoad tries to access the MOC, which hasn't been set so crashes. I'm not sure how to solve this, using the VC, and shifting my start up code into documentIsReady is the only way I have got it to work so far. – DogCoffee Dec 09 '13 at 02:23
  • I still pass the entire document as soon as I create the object, not just the context. By the time fetching finally happens, the context is usually ready. It might not be ready yet if the document isn't open, I'm not sure about that. – Scott Berrevoets Dec 09 '13 at 03:03
  • i just moved all my code into viewdidload, and the objects are not even being saved by core data... close to giving up on this managed document and using normal core data. – DogCoffee Dec 09 '13 at 23:26
  • @Smick: Please update your question with the most up to date code – Scott Berrevoets Dec 09 '13 at 23:37
  • @Smick: you now open your document in viewWillAppear:, that's way too late. Open it in the app delegate, but pass it on (even though it may not be open yet) to the view controller. Then you'll be able to use it as by the time the view appears, it will have opened. – Scott Berrevoets Dec 10 '13 at 02:15
  • Tried that and got same issue, not saving. Feel like I've tried every combo under the sun ... all fail, I'm missing something... – DogCoffee Dec 10 '13 at 06:42
  • I needed this [self.document saveToURL:self.document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) { if(success == YES) NSLog(@"it's saved!"); }]; – DogCoffee Dec 10 '13 at 08:07
  • @Smick: you shouldn't need that. UIManagedDocument supports auto-saving. However, auto-saving happens arbitrarily, so give it enough time (usually 30 seconds is sufficient) to actually save. If you run your app, make changes, then immediately stop and run it again, the changes won't be there because it didn't get the chance to auto-save. – Scott Berrevoets Dec 10 '13 at 13:18
  • yeah, cheers. For debugging, its handy. So thats why my data wasn't saving. I am wondering if I should put that save call in the app delegate, before terminate / or did enter background? Or should I trust the auto save for my release version ? – DogCoffee Dec 10 '13 at 20:52
  • In my experience, auto-save works just fine in release builds. When you kill the app, it appears to be saving right before it's being killed. You can put in some manual saves here and there for debugging purposes, but I usually don't care too terribly much about losing data when I'm debugging. If I do, I just wait a few seconds to give auto-save a chance and then I rerun the app. – Scott Berrevoets Dec 10 '13 at 20:55
2

Create the UIManagedDocument in the AppDelegate but because creation is asynchronous you can't rely on it being ready when your view loads. So you should not try and access anything in your viewDidLoad method. Rather have a method on your viewController called displayData{} or something and call this from the UIManagedDocument open completion handler, or better still post a notification once the file has been successfully created and/or opened. Better get used to this async stuff because iCloud makes extensive use of it. Check this link out for more information on how to achieve UIManagedDocument & iCloud Integration.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
   ...

    if (fileExists) {
        [self.document openWithCompletionHandler:^(BOOL success) {

            NSLog(@"Exsits dont create again");
            if (success) {
               [[NSNotificationCenter defaultCenter] postNotificationName:@"DatabaseReady"
                                                        object:self];
            } else {
               NSLog(@"cound not open document at %@", url);
            }
        }];
    }
    else {
        // create it
        [self.document saveToURL:url forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
            NSLog(@"Create it");
            if (success) {
               [[NSNotificationCenter defaultCenter] postNotificationName:@"DatabaseReady"
                                                        object:self];
            } else {
               NSLog(@"cound not create document at %@", url);
            }
        }];
    }
}

Add an observer in your viewControllers viewDidLoad method to receive the notification

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(displayData) name:@"DatabaseReady" object:nil];

In your viewControllers displayData method get the moc and retrieve your data and you should be good to go.

- (void) displayData
{
self.document = [(AppDelegate *)[[UIApplication sharedApplication] delegate] document];

self.managedObjectContext = self.document.managedObjectContext;

Car *demo = [NSEntityDescription insertNewObjectForEntityForName:@"Car" inManagedObjectContext:self.managedObjectContext];
demo.carName = @"xxxxx ";
}

I would usually also post a (different) message if the creation failed so you can display a message to the user from the viewController.

Duncan Groenewald
  • 8,496
  • 6
  • 41
  • 76