When using Core Data inside a Cocoa app (OS X – 10.10) much like an iOS app you will find that you need to pass around an instance of NSManagedObjectContext between your view controllers. This becomes even more critical if you are using the native Cocoa Bindings ‘shortcuts’ to assign and manage your data to a table.
In my opinion documentation for dealing with Cocoa Bindings when using Storyboards is quite, shall we say ‘sparse’. There are a few schools of thought on the best / easiest way to gain access to the App Delegate’s NSManagedObjectContext needed by the NSArrayController :
Solution 1:
Use the NSManagedObjectContext directly from the AppDelegate from inside the View Controller.
1 2 3 4 5 6 7 |
#import <Cocoa/Cocoa.h> #import "AppDelegate.h" @interface ViewController : NSViewController @property (strong, nonatomic) NSManagedObjectContext *managedObjectContext; @end |
1 2 3 |
- (NSManagedObjectContext *) managedObjectContext { return [(AppDelegate *)[[NSApplication sharedApplication] delegate] managedObjectContext]; } |
This solution works great for simple usage, perhaps best used for single view controller apps, however there are some distinct disadvantages:
- Could be seen as going against Apple’s guidelines
- Code copy and paste – for every view controller these same lines of code need to be applied, this refers to the second block of code and assumes Solution 3 is not also used.
- General code smell, it just feels/is wrong, it’s akin to a global variable. A better alternative would be to use Dependency Injection a.k.a solution >= 2.
Solution 2:
Inject the View Controller with the NSManagedObjectContext from inside the AppDelegate .
1 2 3 4 5 6 |
#import <Cocoa/Cocoa.h> @interface ViewController : NSViewController @property (strong, nonatomic) NSManagedObjectContext *managedObjectContext; @end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#import "AppDelegate.h" #import "ViewController.h" @interface AppDelegate () - (IBAction)saveAction:(id)sender; @end @implementation AppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { // Make sure you get an instance of the main window NSWindow *mainWindow = [[[NSApplication sharedApplication] windows] objectAtIndex:0]; // Extract the root view controller from the main window ViewController *viewController = (ViewController *) mainWindow.contentViewController; // Set the managed object context [viewController setManagedObjectContext: self.managedObjectContext]; } |
This is a simple and clean approach, you are injecting the NSManagedObjectContext into the ViewController from the AppDelegate . Just a quick note for the getting the NSWindow instance, Apple has this note:
The value in this property is
nil
when the app’s storyboard or nib file has not yet finished loading. It might also benil
when the app is inactive or hidden.
To combat this potential problem using something like [[[NSApplication sharedApplication] windows] objectAtIndex:0]; should always return the first window.
Solution 3:
Now whilst the above two solutions successfully deal with allowing your root or 1st view controller access to an instance of the NSManagedObjectContext object, what happens if you have more than one View Controller? One convenient method is to use prepareForSegue like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#import "ViewController.h" #import "SecondViewController.h" @implementation ViewController - (void) prepareForSegue:(NSStoryboardSegue *)segue sender:(id)sender { // Make sure this is the same name as you segue!! if ([[segue identifier] isEqualToString:@"secondView"]) { SecondViewController *controller = [segue destinationController]; controller.managedObjectContext = self.managedObjectContext; } } @end |
You must also add the property for the SecondViewController (of course replace this for the real view controller name):
1 2 3 4 5 6 |
#import <Cocoa/Cocoa.h> @interface SecondViewController : NSViewController @property (strong, nonatomic) NSManagedObjectContext *managedObjectContext; @end |
Now we are getting somewhere, we initially first inject the NSManagerObjectContext into our first (root) view controller from the AppDelegate and then pass this same context to our second view controller through the handy prepareForSegue function, awesome. However for each new view controller which requires the NSManagerObjectContext you will have to always do two things:
- Create the property managedObjectContext (to reference the NSManagerObjectContext ) in the view controller header file.
- Replicate the same logic in prepareForSegue which includes adding the header file for the controller the segue is going to
I don’t think doing the above is a major show stopper, I have seen some examples looping over all the view controllers within the NSWindow object and assigning the NSManagerObjectContext to each of them. To be honest I don’t much like the sound of this as it seems to go against ‘lazy loading’ which Apple seem keen to promote. No doubt these examples could be improved, but for now these options should provide a reasonable start point.