I realize I skipped a step right in the beginning. I’m sure I skipped more steps, but at least I noticed this one. The step I missed is:
How does one create a new window and its associated objects and have it created when a menu option is clicked or another part of the application needs it? We don’t want everything crowded into the MainMenu.nib, so we need to modularize it somehow.
Cocoa, and Core Data with it, is very MVC oriented. MVC stands for “Model-View-Controller”. I’m sure you know what that is, but if you don’t, Google for it now. I’ll wait right here.
….
Everyone back together? Fine. Well, I would very much like to keep the model, the view, and maybe even the controller, together as one recognizable unit. A unit I could choose to include into any other app, if I so wanted. Or at least a unit that is sufficiently coherent and loosely coupled to the rest of the app to make debugging easier.
So far, I think the best compromise is to have the view (the UI part) and the model (the managed objects) in the nib file that belong to the module itself. In my current example, that would be the Countries.nib. So, the countries.nib file would look like this in Interface Builder:
The managed objects aren’t actually in the countries.nib file, but the array controller (CountriesAC) is, and the array controller takes care of talking with the managed objects, so for all practical purposes, it’s one whole. Well, it’s one whole seen from an activation standpoint, anyway. The countries.nib file is loaded, which instantiates the CountriesAC object, which in turn instantiates the managed objects, if they’re not already in memory.
But what loads and instantiates the countries.nib file itself? The CountriesController object of course! A class you haven’t heard of yet (I’m doing a lot of explaining backwards, it seems). The CountriesController is the “C” in “MVC” and takes care of the interaction between the view and the model, on the one hand, and the rest of the application on the other hand. In a “pure” MVC construct, the controller would do all the talking between the model and the view as well, but that seems not to be the case here. A lot of stuff happens automatically between the view and the model in core data.
If the CountriesController is the object that causes the countries.nib file to be loaded, then it can’t be a part of it. It has to live outside it. It will be instantiated by the AppController, an object that is located in the MainMenu.nib file and thus is created automatically as the application starts up. The AppController manages the creation and calling of all controllers of this kind, like the CountriesController, the (yet to be designed) CustomersController, GeneralAccountsController, etc.
We’re going to put the AppController in the MainMenu.nib file, so it’s automatically loaded and instantiated as soon as the application is started.
The AppController is derived from nothing else than the NObject class. To create it, you do “File… New File…” and then under “Cocoa” you select “Objective-C class”. Name it AppController. Click “Next” and you have two new files, AppController.h and AppController.m.
You need to add AppController to the MainMenu.nib file. Double-click MainMenu.nib in XCode and Interface Builder will open it and it will look like this in its default state:
(A little note here: as I go along, I often start over, so the project name changes a litte. Here you see “Acc3” where you saw, and will see “Acc2” in other screens. Pretend you don’t see the difference, even if it becomes “Acc4” or even “Acc5” at times. Call it poetic license.)
Now, open the AppController.h file and edit it to look like the following:
Note that I included a reference to two controllers, one for the countries and one for the customers, which we haven’t done anything for yet. But seeing two of them makes it easier to realize where I’m going with this.
Also note that the two member functions are of type (IBAction). This is necessary for Interface Builder to recognize them as actions and to let us connect them to menu items by using bindings.
Let’s look at the implementation file AppController.m, now:
It’s not hard to see what this one is doing. It’s lazily creating the controllers for countries and customers only if there is ever a request for them.
Instantiating the AppController
Now we need to convince the MainMenu.nib to instantiate the AppController for us. The way to achieve this is by first making the MainMenu.nib aware that the AppController exists. To do that, arrange things so that you have the project open in XCode and the MainMenu.nib open in Interface Builder at the same time on the screen:
Now drag the AppController.h file from XCode and drop it on the MainMenu.nib window. As you do that, the MainMenu.nib window will switch to the “Classes” tab, showing the AppController class:
This only shows that MainMenu.nib knows how the AppController class looks, not that it has it instantiated. (By the way, if you change the header of the AppController class later, you have to do this drag-drop thing again, to update MainMenu.nib’s knowledge of the header file.)
Without touching anything else, select “Classes” from Interface Builder’s menu och select “Instantiate AppController”…
… and the MainMenu.nib window in Interface Builder will show an instance of the AppController class like this:
Creating a menu item
We need to create a menu item now. An item that, when selected, causes the AppController to open the Countries.nib file and show the right window on screen. To do that, open the MainMenu.nib file in Interface Builder, go to the first panel in the widget toolbox and select a “Submenu”:
Now, drag it into the menubar designer and have it placed after the “Edit” submenu:
After dropping it, it should look like this:
Next, doubleclick the “Submenu” item and change its name to “Data”. Then do the same with the subitem, changing it to “Countries”. The end result should look like this:
Connecting the menu item to the AppController
Now, control-drag from the new “Countries” menu item to the AppController object in the MainMenu.nib class window:
This causes the inspector to show the relevant Target/Actions of the target object, that is the AppController, so select “showCountriesPanel:” and either doubleclick it or click the “Connect” button:
(The reason these methods showed up in the target/action pane is because they were labeled “(IBAction)” in the AppController.h header file. Now you see why that had to be so.)
Now, selecting the menu item “Countries” in the main menu will fire the showCountriesPanel method in the AppController object. In the next part of this series, we’ll take it from here, building the CountriesController and connecting the AppController to it.
Martin–
Keep the articles coming. I’m dabbling in Cocoa, CoreData, and bindings. This certainly keeps my interest level high.
Thanks for the kind words. Yes, writing this keeps my own interest up as well. I’ll go on when my taxes are done…
The dragging of the AppController is now obsolete in Xcode 3, I do believe…. I followed the following hint to get the AppController to be in the NIB file…
“Try this (from memory, I’m at work so I may be wrong) From one of the menu’s select “read class file” and read your .h file. Then drag an NSObject from the library to the Document window. It should let you do that. Then open the inspector, and change the object type to AppController.” — http://forums.macrumors.com/showthread.php?t=493327
That’s a relief. That appcontroller dragging thing seemed like such a kludge. But I’ll leave you to it; I’m deep into .NET now (please don’t spit on me).