oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Build an eDoc Reader for your iPod
Pages: 1, 2

Generating Files for AppController

With all of that behind us, we can now generate the controller's code and instantiate it. Select "AppController" from the "Classes" tab in MainMenu.nib, and then go to the "Classes" menu at the top of the screen and choose "Create Files for AppController." Choose the default options that appear to generate the files and insert them into the current target.

Back in Xcode, you can now expand the "Other Sources" folder in the leftmost pane to reveal the AppController.m and AppController.h files. Drag these two files to the "Classes" folder, and inspect them--trying to draw parallels to the work you accomplished in IB. In AppController.h, you should see seven pointers to objects prefaced by the "IBOutlet" macro and two actions prefaced by the "IBAction" macro. Your AppController.m file should have the shells for the two actions. For each of these actions, insert the following log message.

NSLog(@"This %@ button works!", sender);

Instantiating the Controller

We now have a view along with a controller class and its source files, but the view and control are still disjointed. To bridge the gap between them, we need to instantiate the controller in IB. To instantiate it, select "AppController" and then "Instantiate AppController" from the main menu. You should see a blue cube appear in the "Instances" tab of IB with the appropriate label. At this point, we'll connect all of the outlets and actions specified in the controller's code with the controls on our view.

Direction of Connections

When you control-dragged to specify the tabbing outlets earlier, you noticed that it was in a directional manner. The "Source File" button passed control to the "Destination" button, so we dragged FROM "Source File" TO "Destination." In a similar manner, we'll always specify our outlets and actions FROM the sender TO the receiver. Take a moment to let that soak in. It's really important to understand the direction of the connections.

Drag FROM "AppController" TO the NSTextField when setting its outlet.

First we'll connect some of the outlets. Control drag FROM the blue cube entitled "AppController" in the MainMenu.nib palette TO each of the NSTextFields except for the label. You'll see the normal markup and the Info window appear.

Related Reading

Learning Cocoa with Objective-C
By James Duncan Davidson, Apple Computer, Inc.

Under the "Outlets" tab, choose the corresponding outlet. These connections allow the controller to address the NSTextFields in such ways as getting and setting their value. To set the actions for each of the buttons, control drag FROM each of the three NSButtons TO "AppController" in MainMenu.nib. In the Info window that appears, select the "Actions" tab, and connect to the appropriate action by clicking on "Connect." The "openFileDialog" action is used for both the "Source File" and "Destination" buttons.

At this point, you should notice that there are still three outlets that are not connected. To create them, control drag FROM the "AppController" TO each of the buttons and create the connection just like you did for text fields. These outlets are being set separately from the other outlets in order to underscore the difference between outlets and actions. The first set of outlets allows our controller to "know about" the NSTextField objects. The set of actions allows our NSButton objects to "trigger" methods in response to a click.

This final set of outlets allows our controller to "know" about our buttons. Although buttons normally don't do things like change values during an application, we do need to know which button was clicked that triggered an action. For example, the "openFileDialog:" action occurs in response to clicks from both the "Source File" and the "Destination" buttons. In AppController.m, You'll notice that actions pass in the identity of the control that triggered them. In our application, "openFileDialog:" responds differently, depending on which control triggered it.

Build and Run It

You should now be able to run what you have so far in Xcode. Keyboard control should start with the "Source File" button, and you should be able to tab through the buttons and the separator text field in the correct order. When you click on one of the two buttons that are enabled, you should see the correct log message appear. Additionally, you should not be able to type into the text fields just above the "Source File" or "Destination" buttons. If you have issues, start the troubleshooting process with the actions and outlets.

Now is a good time to handle that disabled "Copy It" button. The idea is that no copy can take place until after the user has specified both a source file and a destination directory (the iPod). By our default settings in IB, "Copy It" is disabled, but we'll programatically enable it via its outlet in the controller if both the sourceFile and destDir NSTextFields are not empty. Take a moment to look up NSTextField and NSButton in Xcode's documentation the same way you did for NSProgressIndicator for more insight into how we'll do this.

Ten Minutes Later: In your pursuit of knowledge, you noticed that both NSTextField and NSButton inherit from NSControl, so you looked back to NSControl to find the stringValue: and setEnabled: methods. Thus, we can programmatically enable the copy button as so:

if (! 
	(([[sourceFile stringValue] isEqualToString:@""]) || 
	([[destDir stringValue] isEqualToString:@""]))
	) {
	[copyButton setEnabled:YES];

Programmatically using outlets in the controller to change the view.

Before I can leave you to do some tinkering on your own, we'll need to declare an array and override two methods inherited from NSObject: "init:" and "dealloc:." The "init:" method provides a standard location to initialize objects that require dynamic memory. The "dealloc:" method is the standard location to clean up memory declared in "init." If you"re not familiar with Cocoa memory management, you should have a look at Introduction to Memory Management before next time. Copy the "init:" and "alloc" given below into your project and take a look at what each line of code accomplishes. Comments to guide you are inline.

//In AppController.h
//Declare this array in AppController.h
//Make sure it's inside the curly brackets
NSArray* typesArray;

//Declare these methods in AppController.h
//Make sure they're outside of the curly brackets
- (id)init;
- (void)dealloc;

//In AppController.m
- (id)init {
	//call parent"s init
	[super init];

	//create an array and retain it. Otherwise, it is placed in the
	//autorelease pool and released as soon as this method
	//ends because it's created with a convenience constructor

	//these string values specify the types of files that will be available
	//to select in an NSOpenPanel when the user triggers "openFileDialog"
	typesArray = [[NSArray arrayWithObjects: @"txt", @"pdf", nil] retain];

	return self;

- (void)dealloc {
	//clean up that memory from the retain call in init
	[typesArray release];

	//call parent"s dealloc
	[super dealloc];

To stretch your mind, I'm giving you the code for the "openFileDialog:" method that we'll walk through next time. This method gives you the last big chunk of logic that you need to complete the user interface portion. Specifically, do these things to get rolling:

  • Replace your existing "openFileDialog:" in AppController.m with this new one
  • Annotate the existence of "openFileDialog:" in AppController.h
  • Build and Run the Project to see what it does differently
  • Inspect the "openFileDialog:" code to determine how it works
  • Research NSOpenPanel, NSArray, and NSString in Xcode's documentation.

- (IBAction)openFileDialog:(id)sender { 
	//Research this control
	NSOpenPanel* openPanel; 
	if (0 == [typesArray count]) { 
		typesArray = nil; //allow any type

	//configure the open panel 
	openPanel = [NSOpenPanel openPanel]; 
	[openPanel setAllowsMultipleSelection:NO]; 
	[openPanel setTreatsFilePackagesAsDirectories:NO]; 
	[openPanel setResolvesAliases:YES]; 
	if ([sender isEqualTo:sourceButton]) {
		[openPanel setCanChooseDirectories:NO]; 
		[openPanel setCanChooseFiles:YES]; 
		[openPanel setTitle:@"Source File"];
		//a good method to research in the NSString class
		[openPanel setDirectory:[@"~" stringByExpandingTildeInPath]]; 
	else {
		[openPanel setCanChooseDirectories:YES]; 

		//where does this method come from? It's not in the
		//documentation for NSOpenPanel. Hint: inheritance.
		[openPanel setCanCreateDirectories:YES];

		[openPanel setCanChooseFiles:NO]; 
		[openPanel setTitle:@"Destination Directory"];
		//the iPod should be in this directory, if mounted
		[openPanel setDirectory:@"/Volumes"]; 
	if (NSOKButton == [openPanel runModalForTypes:typesArray]) { 
		NSArray* selection = [openPanel filenames];
		if ([sender isEqualTo:sourceButton]) {
			[sourceFile setStringValue:[selection lastObject]];
		else {
			[destDir setStringValue:[selection lastObject]];
	if  (! 
			(([[sourceFile stringValue] isEqualToString:@""]) || 
		    ([[destDir stringValue] isEqualToString:@""]))
		 ) {
		[copyButton setEnabled:YES];

Next Time

Next time, we'll briefly review some of the "openFileDialog:" code as well as touch on memory management and how it applies in a very limited sense to this project. We'll also implement the bulk of our model--a parser that intelligently segments our documents into logical sections. When we're finished, the project will be fully functional for plain text files. In the final installment, we'll complete the encore portion by using the Cocoa-Java bridge to incorporate an existing open source project that allows us to extract and chunk the text from PDF documents.

Matthew Russell is a computer scientist from middle Tennessee; and serves Digital Reasoning Systems as the Director of Advanced Technology. Hacking and writing are two activities essential to his renaissance man regimen.

Return to the Mac DevCenter