oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Programming With Cocoa Adding Spit and Polish to Your Cocoa App

by Michael Beam

Many classes in Cocoa take delegates that provide guidance to a variety behaviors in a class. NSApplication is one such Cocoa class that may take a delegate -- one that provides many features we have come to expect from any application, but may have been unaware as to how to implement them in Cocoa.

We'll take a close look at two of NSApplication's delegate methods today that allow us to specify whether or not the application should present the user with an untitled document when it launches, and that provides a means for the application to open documents whose icons have been dropped on the application icon.

Additionally, as part of a greater effort to add some polish to ImageApp, we will learn how we can control the document window's title to display arbitrary information. Finally we'll endow ImageApp with a customized about panel.

The Application Delegate

The application delegate controls the behaviors described above by implementing two methods in particular: -application:openFile:, and -applicationShouldOpenUntitledFile:. The first of these tells the application how to open files dropped on the application icon; the second tells the application whether it should open an initial untitled document.

The latter is especially important in an application such as ours where the goal is not content creation, but rather content viewing. In other words, an empty canvas would be superfluous without any way to paint on it.

The first thing you'll probably want to know is how to assign an object as the delegate to our NSApplication instance. Not surprisingly, assigning an NSApplication's delegate is done most easily in Interface Builder (IB). Out of the two nibs we have, MainMenu.nib is the only one that has any relation whatsoever to NSApplication, as NSApplication is the nib's owner (i.e. the class of File's Owner in the nib is NSApplication). We'll work with this nib.

Open up MainMenu.nib in Interface Builder, and under the Classes tab create a subclass of NSObject. Name this subclass AppController, and instantiate it. In the Instances tab connect AppController to the delegate outlet of File's Owner. With that we've designated AppController as the official ImageApp application delegate. Not bad.

Now double-click on AppController to take you back to the Classes tab, and create the files for AppController so we can work with them in Project Builder. Save your work and return to Project Builder.

In general, implementing -applicationShouldOpenUntitledFile: can be as simple as returning YES or NO in response to the question being asked in the method name, or it can be as complicated as looking up a user default and returning a BOOL based on the stored default. More complicated schemes are possible, but likely don't have any practical purpose. By default document-based applications do open new untitled documents, so to disable this behavior we do the following:

- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)theApplication
    return NO;

The second delegate method we are going to implement is -application:openFile:. This method will enable the application to open a file dropped on either its Finder or Dock icons, provided we implement it correctly. So how do we programmatically control the creation of documents from paths and such? Why, with NSDocumentController, of course!

Two columns ago we learned that NSDocumentController plays a critical role in Cocoa's multiple-document system as a document factory. That is, NSDocumentController knows how to respond to actions sent by the Open and New File menu items. We can, of course, invoke file creation and opening methods in NSDocumentController ourselves, which is the route we'll take to implement -application:openFile:.

Everything we need to load the contents of a file into a document is provided with the file path in -application:openFile:. Let's take a look and see how we accomplish this:

- (BOOL)application:(NSApplication *)theApplication
           openFile:(NSString *)filename
    NSDocumentController *dc;
    id doc;

    dc = [NSDocumentController sharedDocumentController];
    doc = [dc openDocumentWithContentsOfFile:filename display:YES];

    return ( doc != nil);

Every document-based application has a document controller, which we retrieve using the class method +sharedDocumentController. Next we send an -openDocumentWithContentsOfFile:display: message to the document controller and assign the variable doc to the returned document object.

This method takes the path of the document, provided by the openFile: parameter of the delegate method, creates a new instance of MyDocument, and tells that object to load the contents of the file as the document's data. If the document controller was not able to load the file, then nil is returned. We can use the value of doc to test whether the operation was successful

The display: argument tells the document object whether it should create the interface to the document -- e.g. the window. If you're wondering why you wouldn't want to show the interface to a document, consider situations when ImageApp is being operated by something other than a user, say by AppleScript. AppleScript certainly doesn't need to view the contents of the document like human users do. All it needs to be able to do is create an instance of the NSDocument subclass for the document, and operate on it.

This leads us into a whole area of application design to support scriptability, which we will delve into further another time. One of the basic tenets of making scriptable applications is that the ability to operate on a document's data should not depend on the presence of the document's user interface -- the document should not need to display its contents to manipulate them. You should keep this in mind when you build your document classes; it will make life easier down the road should you opt to make your application scriptable.

So that was a long answer to the question of why display: would ever be NO. To close out the implementation of this method we check to see if an object is assigned to doc, and return a BOOL indicating the success of the open operation. Voila! Compile it, give it a run, and see how it works for you.

NSApplication declares many other interesting delegate methods you may find useful for your application. We won't get into all of the details, but let me give you an overview of them. The method -applicationDockMenu: returns an NSMenu object that will be displayed as the application's dock menu. This is useful if you want your application to support a dynamic dock menu that changes in response to a changed state of the application. The returned menu can be created programmatically, or it can be loaded from a nib.

Another delegate method some of you may find useful and interesting is -applicationShouldTerminateAfterLastWindowClosed:. The default behavior of Cocoa applications is to stay open even after the last window has closed. However, if you were to build a utility, you could follow the example of the majority of Apple's utilities and have this method return YES.

Thus, when the last window is closed, the application will quit. Now, don't abuse this method. Mac users don't expect their applications to quit when the last window is closed, and would probably be unhappy if random apps did work like this. This is especially true for document-based apps, but not as true for applications that users might run briefly to observe some state of the operating system, as many of the utilities do.

There are also several delegate methods that are sent in response to the application becoming the active application, or resigning this status; there are several sent in response to the application hiding and unhiding; delegate methods are even invoked in response to the application launching and terminating.

The methods related to application-launching could be useful if you wanted to implement a splash screen for your application at launch time. For example, the method -applicationDidFinishLaunching: could be used to close a window serving as a splash screen.

Many of these delegate methods also have accompanying notifications posted to the default notification center when the method is invoked. For example, if you wanted to execute some code in response to the application becoming the active application, you could implement in the application delegate the method -applicationDidBecomeActive:, or you could register an unrelated object in your application to respond to the notification NSApplicationDidBecomeActiveNotification.

The advantage of notifications is that you don't have to explicitly declare an object as a delegate to the application; you only have to register the object to respond to whatever notification you're interested in.

So that's the story with NSApplication's delegate methods. Let's move on now to see how to customize the document window's title.

Pages: 1, 2

Next Pagearrow