oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Working With Bitmap Images; Document-Based Application Redux
Pages: 1, 2, 3, 4, 5

Building ImageApp

To start out, open Project Builder and create a new "Cocoa Document-Based Application" project. Name the project ImageApp. Contrary to experience with previous applications, we will be doing relatively little work in Interface Builder (IB). The user interface for ImageApp is pretty straightforward -- nothing more than a window with a scroll view to hold the image view, and a text field in which the user can change the zoom of the document.

Before building the interface though, let's talk some more about the structure of our application and do a little coding. Our application will consist of three classes named MyDocument, IAWindowController, and IAImageView. These classes inherit from NSDocument, NSWindowController, and NSImageView, respectively. IAWindowController will serve the dual purpose of MyDocument's window controller, and as owner and controller of the user interface. NSImageView is a class whose primary role is to display a single NSImage in a view. We are subclassing it to add a small amount of functionality we will later find useful.

Let's get the chore of creating the class files for IAWindowController and IAImageView out of the way. Create IAImageView by creating an Objective-C NSView subclass from File --> New File.... This creates a subclass of NSView, but we need IAImageView to be a subclass of NSImageView. This is rectified by changing the @interface line in IAImageView.h from @interface IAImageView : NSView to @interface IAImageView : NSImageView. Now create the window controller subclass by choosing the Objective-C NSWindowController subclass from the new file assistant and name it IAWindowController.

Implementing the NSDocument Subclass

Because we're using a custom subclass of NSWindowController as our document window controller, we have to change parts of the default MyDocument implementation so the document loads IAWindowController as its window controller and interface. We're going to remove the method -windowNibName and put in its place the method -makeWindowControllers.

The purpose of -windowNibName is to return the name of the nib containing the document's interface. MyDocument uses this when creating the default NSWindowController used to manage the document's window. In its place we're going to implement a method of NSDocument called -makeWindowControllers:. This method is called when new documents are created or existing documents are loaded -- basically whenever a new instance of the document class is created. All we have to do here is instantiate and initialize IAWindowController and then add it to the document's list of window controllers.

To load a nib file containing the document's interface with an NSWindowController, we use the initialization method -initWithWindowNibName:. This method takes the name of the nib file (minus the extension) as its sole argument, and then IAWindowController takes care of loading the nib, performing any necessary initialization, and displaying the window controller's primary window.

Before we implement -makeWindowControllers, let's change the name of the nib file from MyDocument.nib to IAWindow.nib, to further reflect the idea that an instance of IAWindowController will be handling the nib rather than MyDocument. We will see in a moment how this concept of ownership is implemented in Interface Builder.

So we know how to initialize an instance of IAWindowController; now we need to know how to add that window controller to the document's list of window controllers. To do this we use NSDocument's addWindowController:. Let's create and add a window controller to the document with -makeWindowControllers in MyDocument.m:

- (void)makeWindowControllers
     windowController = [[IAWindowController alloc] initWithWindowNibName:@"IAWindow"];
    [self addWindowController:windowController];

As you can see, windowController is an instance variable that needs to be declared in MyDocument.h as:

IAWindowController *windowController;

Additionally, you need to import IAWindowController.h into MyDocument.h so the compiler knows what's going on with IAWindowController and doesn't feed us a bunch of warnings.

Back to our method. All we did was alloc and init the window controller using the aforementioned -initWithWindowNibName:, passing the name of our nib as the argument-@"IAWindow". After that we sent an -addWindowController: message to self with windowController as the argument. So that's all we have to do in MyDocument to support our NSWindowController subclass.

If you need to do some special initialization within MyDocument directly before or after the window controller loads the nib then NSDocument gives us the option of overriding -windowControllerWillLoadNib: and -windowControllerDidLoadNib:. As the names imply, these are called before and after the nib loads, respectively.

Since our data model will be an instance of NSImage, and in a moment we'll be writing code to create that object when a file is loaded, it would do us well to say a bit about NSImage.


NSImage is the workhorse of image handling in Cocoa. NSImage provides developers with a convenient and easy-to-use front end to a powerful and flexible back end made up of many more classes in the AppKit. One of the key ideas behind NSImage is that of image representations. Think of NSImage as providing a higher-level, more abstract concept of an image, while image representations via NSImageRep and its subclasses provide a more specialized interface to the different ways image data can exist.

This is better understood if you look at the names of the several NSImageRep subclasses: NSBitmapImageRep, NSPDFImageRep, NSEPSImageRep, NSPictImageRep, NSCustomImageRep, and NSCachedImageRep. Each of these knows how to render images with different data formats, and the diversity of these classes is hidden behind the simplicity of NSImage.

Another key idea behind NSImage is that of NSImage keeping multiple image representations for an image. By doing this NSImage is able to provide whatever graphics-rendering device (screen at thousands of colors, screen at millions of colors, printer, plotter, whatever) with the image representation best suited to displaying the image on that particular device. We'll see in the next two columns how we can further exploit this structure for various tasks.

For our purposes today all we need to do is create an NSImage from an image file's data. This is done using the initializer -initWithData:, which is convenient since with -loadDataRepresentation:ofType: we are given an NSData object in the first argument. The data passed in this argument is the data initialized from the file selected in the open panel by the user.

Finishing Up MyDocument

The document's image object will be assigned to an instance variable named activeImage; so let's add this to MyDocument.h:

NSImage *activeImage;

Implementing -loadDataRepresentation:ofType: is done in the following way:

- (BOOL)loadDataRepresentation:(NSData *)data ofType:(NSString *)aType
    activeImage = [[NSImage alloc] initWithData:data];
    return (activeImage != nil);

Notice that the return value is the result of a comparison between activeImage and nil. If -initWithData: is unable to initialize the NSImage with the provided data, then the NSImage object is freed and nil is returned. Therefore, by comparing activeImage to nil we have an indicator of the success of the file-open operation that we can use as the method's return value.

Finally, let's add a method -activeImage to give other objects access to the document's image object. This method will simply return the image instance variable:

- (NSImage *)activeImage
    return activeImage;

You should also declare this method in MyDocument.h so the compiler knows it's there.

Since ImageApp is a viewer application, we will not implement -dataRepresentationOfType:; that will be done in the next column to everyone's satisfaction. With that, we have put in place a skeleton implementation needed to open documents, and to set up the document's window controller. Let's press on with building our interface, and coding IAWindowController.

Pages: 1, 2, 3, 4, 5

Next Pagearrow