|
|
|
|
Building Cocoa Applications: A Step-by-Step GuideBy Michael Mahoney, Simson GarfinkelMay 2002 0-596-00235-1, Order Number: 2351 648 pages, $44.95 US $69.95 CA |
Chapter 5
Building a Project: A Four-Function CalculatorIn this chapter, we'll build a simple Calculator application with four functions: add, subtract, multiply, and divide. When we're done, our Calculator will contain the menu and window shown in Figure 5-1. In the process of building the Calculator, we'll learn about Interface Builder, connections, and some of the commonly used Cocoa Application Kit (AppKit) classes.
Figure 5-1. Calculator application window and menu bar
![]()
We've chosen to build a calculator as the first "real" application in this text for several reasons. First, calculators are familiar; we've all used one, and we sort of know how they work. (When creating an application, the first thing to understand is the problem you need to solve.) Second, calculators are useful. As programmers, we're constantly having to do silly little things like add two numbers together or convert a number from decimal to hexadecimal (the hex part will be built in Chapter 7). It's a tool that you can put to work after you build it.
More importantly, a calculator is a good starting point for budding Cocoa developers. In subsequent chapters, we'll use the Calculator as an infrastructure for learning about Cocoa graphics, printing, multiple windows, file handling, and many other features.
Creating your own calculator puts you in charge of its design. After all, there are many kinds of calculators: some are scientific, some are financial, and some are just simple four-function calculators. Our Calculator will let you key in the sequence "3+4=" by clicking four buttons in a window. The Calculator will display (in order) 3, 3, 4, and 7 in a text output area. If you don't like the decisions we've made and want to change or add functions and features, go right ahead! Our aim is to give you the know-how to create your own applications.
Getting Started: Building the Calculator Project
Follow these steps carefully to get started building your Calculator project:
- Make sure that the Project Builder and Interface Builder icons are in your dock, then launch PB from your Dock.
- Choose Project Builder
Hide Others to simplify your screen.
- Choose PB's File
New Project menu command to begin the process of creating a new project (see Figure 5-2). The New Project Assistant dialog opens, as shown in Figure 5-3.
Figure 5-2. Choose PB's File New Project command to create a new project
![]()
Figure 5-3. New Project Assistant in PB
![]()
- Make sure that Cocoa Application is highlighted, as shown in Figure 5-3, then click Next. The New Cocoa Application dialog shows up, as shown in Figure 5-4.
Figure 5-4. Providing the name and location for a project
![]()
- Type "Calculator" in the Project Name field of the New Cocoa Application panel, as shown in Figure 5-4.
- Hit the Tab key to fill in the second line, as shown in Figure 5-4, and click the Finish button.
PB's main window for the Calculator project opens; it should look similar to the window in Figure 5-5.
Figure 5-5. Main window in PB
![]()
The main window in PB contains several buttons. The four buttons that look like tools (hammer, whiskbroom, etc.) at the left of the window just below its title bar are "action" buttons that can build, clean, run, and debug your project. The five disabled (dimmed) buttons near the top-right corner of the window are used with the debugger (as discussed in the section "Using gdb in Project Builder, Step by Step" in Chapter 2). Going from left to right, these debug buttons allow you to pause execution, continue execution, step over the method or function call, step into the method or function call, and step out of the current method or function call. Descriptions of the buttons pop up as you move the mouse over them.
The rest of the PB main window is divided into two sets of tabbed views. The vertical set of tabs controls what is seen in the lefthand pane of the window.
This pane (or view) can display one of five different types of information:
- The files in your project
- The classes in your project
- The bookmarks that you have set in your project
- The build targets
- Any debugging breakpoints that you may have set
The horizontal tabs near the righthand side of PB's main window display either the Find feature, the Build output, the Run (logged) output, or the Debugger output in the pane that opens above the tabs. Finally, the lower-right corner of PB's main window is where you can browse or edit a file. The first file that is displayed when you create a new project is the Project Builder Release Notes. Normally, you would edit your source code or display Help or AppKit headers in this (text) pane.
When you first see PB's main window, the Files tab is highlighted and groups for five types of files (Classes, Other Sources, etc.) associated with your project are shown. You can click the little gray disclosure triangles next to the labels in this Groups & Files pane to show the names of the files in your project. We'll discuss these different file types later, in "The Files in a Project."
- Click the disclosure triangle to the left of the Resources label in the Groups & Files pane to reveal the MainMenu.nib and InfoPlist.strings files, as shown in Figure 5-5.
The checks next to these files in the target column at the left of PB's main window mean that the files are part of the Calculator target.
- Double-click the MainMenu.nib file icon in PB's main window.
IB will launch and display the MainMenu.nib interface that was automatically created by PB when we created a new Cocoa application. This interface includes a main menu titled "MainMenu" and a main window titled "Window". An associated Nib File window is also displayed in the lower-left corner of the screen, below the new menu.
- To simplify the screen, choose Interface Builder
Hide Others.
Your screen should contain the same objects as the one shown in Figure 5-6 (although probably not in the exact same location, and the Palettes window may show a different palette).
Figure 5-6. IB with the MainMenu.nib file for the Calculator project opened
![]()
Building the Calculator's User Interface
The MainMenu.nib file created by PB and opened in IB above is called, aptly enough, a nib file (nib stands for NeXT Interface Builder--a holdover from the pre-Apple life of this development environment). A nib file stores information about all of the user interface objects in your program, including the windows, controls, and menus; the connections between those objects; and some other objects that IB knows about. When you compile and link the application you are building, the application's nib file (or files, if the program uses more than one) gets bundled together with the program's executable code and stored in a package, or app wrapper, folder. This folder has a .app extension and looks like an executable application in the Finder.
The nib files are stored in an undocumented Cocoa proprietary binary format. Fortunately, it doesn't need to be documented--all of the nib-file management is done by IB. IB is basically a nib editor: when it opens a nib file, it reads the specifications and displays the associated objects. After you make your modifications to the program, IB writes out a new nib file, replacing the old one.
Now that we've created the project, we'll add and customize the windows, panels, and menus needed for our Calculator's user interface.
Customizing the Main Window
The main window in the Calculator's interface, currently titled "Window", doesn't look anything like a calculator: it's the wrong shape, it shouldn't have a resize handle, and it doesn't even have the right name! Fortunately, these are all properties that we can easily change by using IB's NSWindow Info dialog.
To see this Info dialog for a particular window, you must first select the window by either clicking in its background or clicking on its icon in the Nib File window's Instances pane. If you click on an object (e.g., a button) inside a window object in IB, the button, not the window, will be selected.
In general, the title and contents of IB's Info dialog change in response to which object in the interface is selected. When the Info dialog changes in response to a selection, you may still have to choose which aspect of the object you want to inspect: its attributes, its connections, or something else. You can make this choice by dragging to it in the Info dialog's pop-up menu or by typing Command-1 for Attributes, Command-2 for Connections, and so on.
Next, we'll go through the steps to customize our Calculator's window in IB.
- Select the newly created empty window in IB by clicking in its background.
- Choose Tools
Show Info to display the NSWindow Info dialog. If necessary, press the pop-up list button in the NSWindow Info dialog and select Attributes. (You can accomplish both of these actions by simply typing Command-1.)
The NSWindow Info dialog should now look like the one shown on the left in Figure 5-7.
Figure 5-7. NSWindow Info dialog before (left) and after (right) changes
![]()
- Change the title from "Window" to "Calculator" and hit Return.
- Turn off the Close and Resize attributes in the NSWindow Info dialog's Controls box by clicking their checkboxes so the checkmarks disappear (see the resulting Info dialog on the right side of Figure 5-7--the arrows indicate where the changes were made).
Although the red close button and resize handle do not disappear from the Calculator window in IB, they will no longer be present when the application is running.
- Resize the Calculator window so that it is about three inches square.
Adding Controls in a Window
Next, we'll drag the buttons and text display area that the Calculator application will need from IB's Palettes window into the main Calculator window:
- Make sure the Cocoa-Views palette is visible by clicking the Views button at the top of IB's Palettes window.
- Drag an NSTextField object from the Palettes window and drop it near the top-right corner of the Calculator window. Use the blue guidelines to position the object. (If you release the dragged object when it is near a guideline, the guide will actually grab and align the object, helping make your layout visually attractive.)
When you are finished, your window should look like the one shown on the left in Figure 5-8. A border in the current selection color surrounds the NSView object that is ready to accept the new NSTextField.
Figure 5-8. Calculator window with new text field (left) and button positioned using guidelines
![]()
- Drag the NSTextField's left-middle selection handle to the left to widen the NSTextField object so that it is almost the width of the Calculator window, as shown in the window on the right in Figure 5-8.
- Drag an NSButton object from the Palettes window and drop it in the lower-left corner of the Calculator window, as shown in the window on the right in Figure 5-8. Use the blue guidelines.
- Double-click the center of the NSButton object, change the text "Button" to the digit "0", and hit Return.
- Make the width of this button smaller by clicking the button once and then dragging the button's right-middle handle to the left until the button stops getting smaller. (When necessary, you can make buttons even smaller using the NSButton Info dialog.)
- Make sure the cursor is positioned in the window, and press the Option key on the keyboard to see the layout information, as shown in the window on the left in Figure 5-9. Note that the guidelines gave us a 20-pixel buffer between the button and each edge near it. Release the Option key.
Figure 5-9. Layout information (left) and creating an NSMatrix of NSButtons (right)
![]()
Next, we'll create the Calculator's keypad, using the great power of IB!
- While pressing the Option key on the keyboard, drag the upper-right handle of the NSButton up and to the right. Release the mouse button when there are four rows and three columns of buttons, as shown in the window on the right in Figure 5-9.
Congratulations--you've just created a matrix (NSMatrix) of buttons (NSButtons)! The NSMatrix is one of the classes provided by the Cocoa Application Kit. An NSMatrix object is a two-dimensional array containing other objects that are subclasses of the NSCell class.
Every Cocoa NSControl subclass, including NSButton, NSSlider, and NSTextField, has an associated NSCell subclass (e.g., NSButtonCell, NSSliderCell, and NSTextFieldCell). These cell objects do the actual drawing of the controls that we put into the window. When you drag a button, slider, or text field off the IB palette and into your window, you are actually dragging out two objects--an NSControl and a corresponding NSCell.
You can also display NSCell objects in a rectangular NSMatrix. As before, the NSCell objects handle the drawing. When you drag one of the resizing handles with the Option key pressed, IB automatically converts the NSControl and its associated NSCell into an NSMatrix and a whole set of NSCell objects.
The NSControl object is used for handling events from the keyboard or mouse. IB hides this split between the NSControl and NSCell from us and makes the control and its associated cell look like a single object. This is often a source of confusion for programmers new to Cocoa.
Cells and Controls
Now we'll resize the NSMatrix as a whole to fit the area we want:
- Drag the right-middle handle of the NSMatrix to the right so that the NSMatrix is almost the same width as the NSTextField. This time, all 12 of the buttons titled "0" will get wider simultaneously. (Don't worry about being exact at this point in the interface.)
NSMatrix Dragging Options in IB
When you drag a handle on a matrix object, one of three things can happen, depending on which modifier key is pressed (we saw the first two of these in the previous example):
- None
- Changes the size of all cells in the matrix
- Option (Alt)
- Changes the number of cells in the matrix
- Command (Apple)
- Changes the spacing between cells
These values can also be changed using the NSMatrix Info dialog.
The buttons in the NSMatrix we created will be used to represent digit keys on our Calculator, and thus we'll change their names from "0" to the 10 decimal digits (and disable the remaining 2 buttons). We also need to set some less obvious attributes of the buttons, called tags, to make the buttons work properly. In order to explain how tags work and help you better understand why we make certain choices while creating an interface, we'll postpone finishing the interface for now to discuss the Objective-C class that we'll create to handle the button clicks.
Building the Calculator's Controller Class
It's time to start thinking about the Objective-C object that will control our Calculator--that is, respond to button clicks, calculate the values that the user wants, and display the results. By convention, this kind of object, which performs behind-the-scenes work and communicates with the user interface, is called a Controller.
Controllers generally don't have main event loops; instead, they perform actions in response to events that are received and interpreted by other objects. A good rule of thumb is to place as little code in your Controller as is necessary for it to do its job. If it is possible to create a second Controller that is used only for a clear, particular purpose, do so--the less complicated you make your application's objects, the easier they are to debug. In addition to controlling the overall flow of the application, Calculator's Controller will contain the code to perform the basic arithmetic and thus can be thought of as the computational engine or back end (albeit a very simple one) of the application.
Designing the Controller Class
Cocoa doesn't provide you with a Controller class--it's up to you to write one for your application. (IB and the AppKit are fabulous, but they can't do everything for you--at least not yet!)
Before you start coding, it's a good idea to sit down and think about your problem. What does the Controller have to do? What kind of messages will it need to respond to? What kind of internal state does it have to keep in order to perform those functions? Recall that we want our Calculator to allow a user to type in the sequence "2*5=" by clicking four buttons in a window and to display (in order) 2, 2, 5, and 10 in a text output area. Thus, for our Calculator, the answers are fairly straightforward.
Here's what our Calculator must do:
- Clear the display and all internal registers (value holders) when a "clear" button is clicked.
- Allow the user to click a digit button on the numeric keypad and display the corresponding digit immediately after it is typed.
- Allow the user to click a function button (e.g., "add", "subtract").
- Clear the display when the user starts entering a second number.
- Perform the appropriate arithmetic operation when the user presses the "equals" button or another function button.
Our Calculator must also maintain the following state to perform these functions:
- The first number entered
- The function button clicked
- The second number entered
It turns out that to work properly, our Controller object needs two more pieces of information:
- A flag that indicates when a function button has been clicked--if the flag is set, the text display area (which we'll call readout) should be cleared the next time that a digit button is clicked, because the user is entering a second number
- The location in the readout text display area where the numbers should be displayed
These bullets indicate that we are using Objective-C to create a simulation of a real, physical calculator. That's what object-oriented programming is often about: constructing progressively better simulations of physical objects inside the computer's memory, and then running them to get real work done. When the simulation is functionally indistinguishable from the real-life object being simulated, the job is finished.
Creating the Controller Class
Every Objective-C class, except NSObject, is based on (and inherits from) another class. The NSObject class itself is the most fundamental Objective-C class, because it defines the basic behavior of all objects and is at the root of all inheritance hierarchies. Because we don't need any special behavior in our Calculator other than what is already defined in the AppKit, our Controller class will be a subclass of NSObject.
We'll start building our Controller class by subclassing it from the NSObject class in IB.
- Click the Classes tab in IB's Nib File window to view the AppKit's object hierarchy.
- Scroll to the far left in the Classes pane using the horizontal scroller at the bottom of the Nib File window, and then select the NSObject class by clicking it. (See Figure 5-10.) You can also rapidly jump to the NSObject class by typing the word "NSObject" into the Classes pane's Search field.
Figure 5-10. NSObject (root) class (left) and new Controller classes (right)
![]()
The NSObject class name is displayed in gray, which means that you can't change any of its properties or built-in behaviors without subclassing it. So that's what we'll have to do.
- Click IB's Classes menu item at the top of the screen, then choose Subclass NSObject, as shown in Figure 5-11 (or simply hit the Return key when NSObject is highlighted).
Figure 5-11. Classes menu in IB
![]()
- A new class called MyObject will appear under NSObject in the class hierarchy.
- Change the name from "MyObject" to "Controller", and hit Return. (See Figure 5-10.)
You've just created a new Objective-C class called Controller. Right now it doesn't do anything different from the NSObject class. Next, we'll give the Controller class some custom behavior by adding some outlets and actions.
Outlets and Connections
Cocoa uses a powerful system known as "outlets and connections" to give you an easy way to send messages between user interface objects such as windows, buttons, other controls, and your own custom objects. An outlet is simply an instance variable in an Objective-C class that has the type id and thus can store a pointer to an object. The value of this instance variable is usually set to the id of another object in the nib--that is, a user interface object. Thus, outlets normally point to interface objects.
When an outlet is set to store the id of another object in the nib file, IB calls this a connection. Cocoa maintains connections for you. When object specifications are saved in a nib file, the connections you set up between them in IB are saved as well. These connections are automatically restored when the nib file is loaded back into IB.
Outlets can also be given a specific type. When you do so, IB will give you a warning if you attempt to connect the outlet to an object that is not of that type (or a class of that type).
For example, suppose that you have two object specifications in a nib file: objects A and B. Suppose also that object A contains an outlet, or id variable, that points to object B. When Cocoa loads this nib file, it will first create new instances of object A and object B, then will automatically set the outlet in object A to point to object B; that is, it sets the outlet A to be the id of object B.
Outlets therefore give you an easy way to track down the ids of objects that are dynamically loaded with nib files. They are the mechanism that Cocoa provides for wiring up an interface in IB without writing any code.
Adding Outlets to an Object
There are two ways to add outlets to a class: either by entering them in IB's Class Info dialog, or by hand, using an editor to type them into the class interface (.h) file for your class. In the latter case, you can choose IB's Classes
Read Files menu command to inform IB about the outlets that exist for the class. We'll see how to add outlets using IB in this chapter and by hand in the next chapter.
After adding an outlet, you use IB to initialize where it points. You do this by setting up a connection from the object containing the outlet to the object to which you want it to point, and then choosing the outlet from the list of outlets in IB's Connections Info dialog. When you make a connection between an outlet in an object and another object in IB, IB sets the instance variable in the first object to the id of the object to which it is connected. That's all!
In the following steps, we'll add and initialize an outlet called
readoutin IB.
- If necessary, select Controller in the Classes pane of IB's Nib File window.
- Now type Command-1 to display IB's Attributes Info dialog for the Controller class. The Info dialog window should be titled "Controller Class Info".
- Make sure that the Objective C radio button is selected in the Attributes Info dialog, as shown in Figure 5-12.
- Click the Add button at the bottom of the Controller Class Info dialog, and the outlet called
myOutletwill appear.
- Change the name of this outlet to "readout" by typing the new name followed by Return.
If you make a mistake, you can double-click the outlet to change the name again. You can also click the Remove button to remove an outlet. When you're done, the Class Info dialog should look like the one on the right in Figure 5-12.
Figure 5-12. Creating an outlet in the Controller Class Info dialog
![]()
We'll eventually set the
readoutController outlet to point to the Calculator's text display area (NSTextField) object in the Calculator window. Then the Controller will be able to send messages to the NSTextField via the outlet.Next, we'll add action methods to the Controller class.
Adding Actions to the Controller
An action is a special type of Objective-C method. Action methods are special because they take a single argument called sender, the id of the object that sent the message invoking the action method. Using IB, we can arrange for an object's action method to be invoked automatically in response to a user event, such as a button click, menu choice, or slider drag. Thus, an action method is an event handler.
In Chapter 3, we used the takeIntValueFrom: action method to make an NSTextField automatically take its value from the NSSlider object when the slider knob was moved (see Figure 3-16). Here, we'll create our own action methods in the Controller class and arrange to have them invoked when the user clicks our Calculator's buttons.
- Click the 0 Actions tab in the Controller Class Info dialog.
- Click the Add button at the bottom of the Controller Class Info dialog; the myAction: action will appear, as shown in Figure 5-13.
Figure 5-13. Creating an action (left) in the Controller Class Info dialog; four actions in Calculator (right)
![]()
- Rename myAction: as clear: and hit Return.
- Add the clearall:, enterdigit:, and enterOp: actions to your Controller class in a similar fashion. You don't have to type the colons ( :) when renaming actions, because IB will automatically append them.
Notice that IB alphabetizes the actions as you add them. Your Controller Class Info dialog should now look like the one on the right in Figure 5-13.
In light of our discussion of the design of the Controller class, the function of these four actions should seem fairly self-evident. We'll go over the details later.
Notice that there's only one action to handle all of the digit button clicks ( enterDigit:) and only one action to handle all of the function buttons ( enterOp:). We'll determine which digit or function button is clicked by using the single argument of these actions, the id of the message's sender. By querying the sender of the action message, the enterDigit: and enterOp: methods can determine which digit or function button was clicked. The method will then perform the appropriate action. This is a much more economical means of method dispatch than creating a separate method for each button on our Calculator--it takes less code and it runs virtually as fast.
Creating the Controller Class Files
Now that we've set up an outlet and several actions for our Controller class, we need to tell IB to create the Controller.h class interface file and the Controller.m class implementation file. Then we'll add the appropriate functionality (code) to these class files and eventually compile them with the Objective-C compiler. IB's Create Files for Controller command in the Classes menu generates these files from the class specifications we made in the Nib File window and the Class Info dialog.
- Make sure the Controller class is selected in the Classes pane of the Nib File window.
- Choose Classes
Create Files for Controller.
A sheet will unfold from the Nib File window enabling you to specify the filenames for the class files to be created, as shown in Figure 5-14. Because the name of the class is Controller, the default names for the class files are Controller.h and Controller.m. After creating these class files, IB will insert them into the Calculator project.
Figure 5-14. Saving the Controller class files created by IB
![]()
- Click Choose on the sheet to use the default filenames (Controller.h, Controller.m) and insert them into the Calculator project.
- Now click the PB icon in your Dock to see how these class files fit into your project.
The two new files should be located in the Classes group but may be located in the Other Sources group. If you like, you can move these files from one file group to another in PB's Groups & Files pane; the organization is for your benefit only and is ignored by PB. When you have a large project, you may even want to create your own groups and subgroups of files.
These new Controller class files contain only a skeleton of what we want in the Controller class. To make our Controller work, we have to add some logic and write some Objective-C code.
- Click the Finder icon in your Dock and investigate which files have been created as part of your Calculator project--there are several!
These project files reside in your ~/Calculator folder. We recommend that you compare the files in the ~/Calculator folder in the Finder with those listed in the Groups & Files pane in PB.
Adding Code to Make the Controller Class Work
To make the Controller work, we need to understand a little bit about a four-function calculator. The basic four-function calculator has three registers: an X and a Y register, both of which hold numbers, and an operations register, which holds the current operation. The readout always displays the contents of the X register. Clicking a function button stores that function in the operations register and sets a flag. If the flag is set, the next time a digit button is clicked, the number in the X register is moved to the Y register and the X register is set to 0.
We'll get the Controller class working in stages, testing them one at a time. Generally, this is a good approach to writing any program, large or small. Object-oriented programming makes it easy to test the individual parts, because they are all fairly self-contained.
First, we'll get numeric entry and the clear keys working. Later, we'll handle the arithmetic functions.
- Back in PB, click Controller.h in PB's Groups & Files pane to open the file in PB's main window, as shown in Figure 5-15. (If you double-click instead of single-click Controller.h, a separate editor-type window will open.)
Figure 5-15. Controller.h interface file in PB
![]()
Looking at the code in PB's window (in Figure 5-15), note that the Controller class is a subclass of NSObject, as we specified in IB. Note also that Objective-C declarations have been generated for the outlet (
readout) and the four action methods in the Controller class that you set up in IB.Following is the Controller.h file. The lines generated by IB are shown in regular type, and the lines that you need to insert are shown in bold type.
/* Controller.h */#import <Cocoa/Cocoa.h>@interface Controller : NSObject{IBOutlet id readout;BOOL enterFlag;BOOL yFlag;int operation;double X;double Y;}- (IBAction)clear:(id)sender;- (IBAction)clearAll:(id)sender;- (IBAction)enterDigit:(id)sender;- (IBAction)enterOp:(id)sender;- (void)displayX;@endIB generated the first two (non-bold) lines because we subclassed NSObject to create the Controller class (importing <Cocoa/Cocoa.h> includes the NSObject class interface file, as well as the rest of the Cocoa classes). Because we added
readoutas an outlet in the Class Info dialog, IB also generated the IBOutlet id declaration for it (see the sidebar "IBOutlet and IBAction"). Finally, IB generated the four action method declarations because we added the four actions in IB's Class Info dialog. Note that the single argument for all of these action methods,sender, was generated automatically.
- Insert the five new instance variables and one new method indicated by the lines shown earlier in bold type in the Controller.h file. We'll discuss the new displayX "non-action" method a bit later.
- Save the Controller.h class file (Command-S).
- Still in PB, double-click Controller.m in the Groups & Files pane to open a new editor-type window with the Controller implementation code inside. (See Figure 5-16.)
Figure 5-16. Controller.m interface file in a PB editor window
![]()
IBOutlet and IBAction
@interface ATestObject : NSObject{IBOutlet id anOutlet;}- (IBAction)anAction:(id)sender;@end
@interface ATestObject : NSObject{IBOutlet NSWindow *anOutlet;}- (IBAction)anAction:(id)sender;@endFollowing is the Controller.m file. As with the Controller.h file, we list the lines generated by IB in regular type and the lines you need to insert in bold type.
/* Controller.m */#import "Controller.h"@implementation Controller- (IBAction)clear:(id)sender{X = 0.0;[self displayX];}- (IBAction)clearAll:(id)sender{X = 0.0;Y = 0.0;yFlag = NO;enterFlag = NO;[self displayX];}- (IBAction)enterDigit:(id)sender{if (enterFlag) {Y = X;X = 0.0;enterFlag = NO;}X = (X*10.0) + [ [sender selectedCell] tag];[self displayX];}- (IBAction)enterOp:(id)sender{}- (void)displayX{id s = [NSString stringWithFormat:@"%15.10g", X ];[readout setStringValue: s];}@endIB generated the line that imports Controller.h because every class implementation file must import its own interface file. Most of the other lines generated by IB are simply stubs for the action methods that we set up in the IB's Class Info dialog. IB generates code in class files more for convenience than for any other reason.
- Insert the code shown above in bold type into the Controller.m file.
- Save the Controller.m class file (Command-S).
The Controller class sends messages to instances of the NSTextFieldCell and NSMatrix classes. In particular, the newly added displayX method displays the contents of the X register by sending the setStringValue: message to
readout, the outlet that we created in IB. Later we'll use IB to initializereadoutto point to the NSTextFieldCell object (near the top of the Calculator window).When a message is sent to an object of a class, the class interface file definition for that class should be
#import-ed in the class definition. But#importstatements for the NSTextFieldCell and NSMatrix class interface file definitions are not listed in the code we've shown. What's going on? Fortunately, the#importController.hline in Controller.m, together with the#import<Cocoa/Cocoa.h>line in Controller.h, takes care of importing the NSTextFieldCell and NSMatrix class interface file definitions for us. In fact, they import all the Application Kit class definitions.In Cocoa, the AppKit class headers are all precompiled, so it's quite fast to import them all, provided that <cocoa/cocoa.h> is imported before any symbols are
#define-d. (A precompiled header file has been preprocessed and parsed, thereby improving compile time and reducing symbol table size.) This is why IB inserts the#import<Cocoa/Cocoa.h>line in all class interface files it generates.The clearAll: method in the Controller.m file sets the X and Y registers to 0.0 and the two flags to false, and then sends the displayX message to
self(the Controller object itself ) to display 0.0 in the text display area. The clear: method is similar but only needs to set the X register to 0.0 and then redisplay. We'll discuss the enterDigit: and enterOp: methods after we finish setting up the user interface and making all the connections.Customizing Buttons and Making Connections
In this section we'll use IB to add more interface specifications to the Calculator.nib file, including customizing buttons further and making several different types of connections between objects. In order to make connections which involve an object of the Controller class, we need a representation of it in IB.
Instantiating (Creating an Instance of) the Controller Class
Creating the Controller class isn't enough: we also need to create an object that is a member of this class, called an instance. Then we have to arrange for the numeric keypad of buttons in the Calculator window to send action messages to the instance whenever these buttons are clicked.
- Make sure the Classes tab is displayed in IB's Nib File window (titled "MainMenu.nib"), then select the Controller class (recall that it's a subclass of NSObject). If you have trouble finding it, use the Search feature in the window.
- Choose IB's Classes
Instantiate Controller menu command.
This will create an icon called "Controller" under the Instances tab in the Calculator's Nib File window, as shown in Figure 5-17 (IB automatically displays the Instances tab view). This icon represents an instance object of the Controller class--it can be used as the target object of action messages and also to initialize outlets. You can change the name from "Controller" to something else if you like--the name isn't used for anything except your convenience in IB. Note the little circles with exclamation marks in them next to the instances in Figure 5-17. They represent tiny alerts that can be discovered by mousing over the instances. For example, if you mouse over the Controller instance, you will discover that its
readoutoutlet is unconnected (see Figure 5-17).
Figure 5-17. New Controller instance object icon in Calculator's Nib File window
![]()
Setting Up Tags and Titles for the Keypad Buttons
Next, we'll set the buttons on the Calculator's numeric keypad to the digits 0 through 9 and arrange for each one of them to send a message to the Controller object.
A tag is a reference integer for a Control object that can be set and read in IB's Info dialog. The purpose of tags is to allow your program to distinguish cells in a user interface from one another, which lets you use the same Objective-C method to handle several different but closely related functions.
To differentiate between the buttons, the Controller object will read the tag of each button.
- Double-click the button at the upper-left corner of the matrix in the Calculator window.
The button highlights with a darker version of your current selection color to indicate that it is selected, and the NSButtonCell Info dialog displays information about the button (see Figure 5-18).
Figure 5-18. Setting the title and tag of a Calculator button in IB
![]()
- If necessary, select Attributes in the pop-up menu in the Info dialog. Note how the window changes to show information about the selected object.
- Change the title of the NSButton from "0" to "7" in the Info dialog and hit Return.
- Change the tag of the NSButton to "7" at the bottom of the Info dialog and hit Return.
When the Calculator is running and one of the buttons in the matrix of digit buttons is clicked, we'll arrange for the NSMatrix object to send the enterDigit: message to our Controller object. The Controller object needs to know which button (i.e., which digit) was clicked, so it will send a message back to the sender (the NSMatrix object) to determine which of the button cells in the NSMatrix object was selected. The Controller can then get the tag for that cell and use it in the enterDigit: method as if it were a digit (which is why we set the tags equal to the value of the NSButton).
- Change the titles and the tags of the other keypad buttons to reflect the digits that they represent, as shown in Figure 5-19. You can use the Tab key to move between buttons in the NSMatrix, but setting the tags is a bit of a nuisance. (Make sure you set the tags properly--we've found that this is an easy place to make a mistake.)
Figure 5-19. Making two buttons transparent and unenabled in IB
![]()
- Select the lower-left button in the NSMatrix (it's invisible in Figure 5-19).
- In the NSButtonCell Info dialog, deselect the Enabled switch to make the on-screen button in our running Calculator unclickable.
- Now click the Transparent checkbox in the same NSButtonCell Info dialog to make the button disappear, as in Figure 5-19.
- Repeat the previous two steps for the lower-right button, so that only 10 of the original 12 buttons in the NSMatrix are used. (See Figure 5-19.)
- Click in the Calculator window background (where there are no buttons or text) to select the window, then click the button matrix once to select the NSMatrix as a whole.
- Type Command-T to bring up the Font panel.
As with most Cocoa applications, IB lets you change the font family, typeface, and size of most text it displays. Here we want to change the way our application looks.
- Choose the Lucida Grande 14-point font. (You may have noticed that if you make the text inside a standalone button too large for the button to display the information, IB automatically resizes the button. However, IB does not automatically resize the cells inside an NSMatrix if the text is too large to display. This behavior is somewhat inconsistent and may be changed in the future.)
Making the Connections
- Connect the NSMatrix button object to the Controller instance object by pressing the Control key on the keyboard and dragging the mouse cursor from the middle of the NSMatrix to the Controller instance object icon in the Nib File window, as shown in Figure 5-20.
Figure 5-20. Target/action connection from NSMatrix to Controller instance
![]()
When you make the connection between the NSMatrix and the Controller instance, it is very important that you press the mouse button when the mouse cursor is over the NSMatrix and release the button when the cursor is over the Controller icon. The order matters, because the NSMatrix will be sending a message to the Controller, not the other way around. (Later in this chapter, we'll drag the connection in the other direction to accomplish something else.)
Figure 5-20 shows the resulting "connection wire" between the NSMatrix and the Controller instance icon. The small square in the middle of the NSMatrix indicates the source of the connection, while the square around the Controller instance icon indicates the target. Make sure you have connected the whole NSMatrix and not one of the individual buttons in the NSMatrix.
After you release the mouse button, IB will display the NSMatrix Info dialog (see Figure 5-20). You can determine the source object of the connection by the name in the title bar of the Info dialog--in this case, it should be "NSMatrix Info". The column in the window should include the four action methods we set up earlier in the destination object, namely the Controller instance.
- Click the enterDigit: action method and then click the Connect button in the NSMatrix Info dialog. The dimple next to the method indicates that the connection was made. See the NSMatrix Info dialog at the right in Figure 5-20.
When the Calculator program is running, the connection we just made means the following: whenever a user clicks any one of the 10 on-screen digit buttons, the NSMatrix object will send the enterDigit: action message to an instance of our Controller class. The Controller instance is the target and the enterDigit: method is the action.
WARNING: It's easy to connect the wrong objects or to disconnect a connection you want, so be extremely careful when making connections and check them in IB if your application isn't responding properly.
Next, we'll add clear and clear all buttons in an NSMatrix object, as we did with the digit buttons. This time, however, we'll connect each button to the Controller object individually--we will not connect the NSMatrix.
- Add a matrix of two buttons above the digits matrix in the Calculator window, using the same technique we used earlier (drop a button from the Cocoa-Views palette into the Calculator window and then Option-drag on a selection handle to get two buttons in a matrix).
- Rename the two new buttons "C" (for clear) and "CA" (for clear all) and set the font to Lucida Grande 14, the same as for the digit buttons.
Your window should now look similar to the one in Figure 5-21. If you don't remember how to add a matrix to your window, refer back to "Adding Controls in a Window." If you can't find the Palettes window, choose IB's Tools
Palettes
Show Palettes menu command.
Figure 5-21. Adding another NSMatrix of NSButtons to the Calculator window
![]()
- Double-click the CA on-screen button to select it. The NSButtonCell Info dialog (not the NSMatrix one) should appear.
- Connect the CA button to the Controller instance object icon by Control-dragging from the button to the icon and then double-clicking the clearAll: action in the NSButtonCell Info dialog (double-clicking has the same effect as clicking the action name and then clicking the Connect button). See Figure 5-22.
Figure 5-22. Target/action connection from NSButtonCell to Controller instance
![]()
- Similarly, double-click the C on-screen button to select it and then connect it to the Controller instance icon. This time, double-click on the clear: action to complete the connection.
You can make IB show you an existing connection from a source object by first selecting the source object and then clicking either the target or the action method with a dimple in the Connections Info dialog. You might try this by selecting the CA button again and single-clicking the clearAll: action method. Don't double-click clearAll:, because that will break the connection.
Next, we'll set attributes of the Calculator's text (actually numeric) display area:
- Select the NSTextField object that is the white display area in the Calculator window.
- If necessary, select Attributes in the NSTextField Info dialog (or type Command-1).
- Deselect the Editable option in the NSTextField Info dialog (near the bottom) so that the text in the NSTextField object is not editable by the end user of the Calculator.
Be sure that you leave the Selectable option enabled, as this makes it possible for your user to copy the answer into another application. Many programmers inadvertently make their text not selectable or editable, which can produce significant frustration on the part of users!
- Set the alignment to be right-justified by clicking the icon that looks like this:
- If necessary, resize the window. Also, resize the text field so that it goes across the top of the window but stays within the blue guidelines.
The NSTextField Info dialog should now look like the one in Figure 5-23.
Figure 5-23. Setting the attributes for the NSTextField in the Calculator
![]()
The source of all three Calculator connections we've made so far has been a user interface object, while the destination has always been the Controller object. In the next connection we make, the direction will be reversed--the source will be the Controller and the destination will be an interface object. This second type of connection requires an outlet, and we will refer to it as an outlet connection. Fortunately, we've already declared the outlet that we need (
readout).
- Control-drag from the Controller instance object icon to the NSTextField object. The connection wire (in your current selection color) should look like the one in Figure 5-24.
Figure 5-24. Outlet connection from Controller to NSTextField
![]()
- In the Connections Info dialog, double-click the
readoutoutlet to complete the connection, as shown in Figure 5-24.
Connecting the
readoutoutlet to the NSTextField object causes thereadoutinstance variable in the Controller object to be initialized to the id of the NSTextField when the nib section is loaded at runtime. Initializing an outlet in an instance object is the only way to determine the id of an object created with IB, so outlets must be used when sending messages to user interface objects.
- Type Command-S in IB to save the MainMenu.nib file.
Compiling and Running a Program
At this point, we're ready to test the keypad of digit buttons. To do this, we must compile the Controller.m and main.m source code and link them together with the MainMenu.nib file. (We'll discuss the main source file later, in the section "The Files in a Project.")
There are three ways to compile a Cocoa program:
- From Project Builder
- From a command-line prompt
- From GNU Emacs
We'll describe each of these approaches in the following sections.
Compiling and Running a Program from PB
The following steps will compile (make, build) and run your Calculator program directly from PB:
- Activate PB and click the horizontal Build tab near the right side of PB's main window to see the Build pane (above the tabs).
- To have PB include debugging information in the executable, you must select the vertical Targets tab and make sure that the Development build target is selected, as shown in Figure 5-25.
Figure 5-25. Compiling the Calculator application in PB's main window
![]()
- Start the compilation process by clicking the Build button near the upper-left corner in PB's main window. If you haven't saved all of your files, PB will prompt you to save them before building. In this case, click the Save All button.
If there are no compile-time errors, you should see "Build succeeded" in the lower-left corner of PB's main window and the compile log. If an error occurred, first check the code in your Controller class files and then refer to the next section in this chapter.
- Run your program directly from PB by clicking the build and run button near the top-left corner of PB's main window.
This will run the Calculator.app executable file, which was created in your ~/Calculator/build folder when the Calculator program successfully compiled. (The .app extension doesn't appear in the Finder.) We didn't actually have to compile and run in a two-step process; we only needed to click the build and run button.
The main Calculator window and menu appear on the screen, as shown in Figure 5-26.
Figure 5-26. Calculator running on the Mac OS X desktop
![]()
- Choose Calculator
Hide Others to simplify your screen.
Note that Calculator is running as any other Mac OS X application runs, and we get the menu bar for free! Note also that the position and size of the Calculator window and its contents are the same as the way you left them in IB.
- Try the keypad buttons to make sure every digit works. Clicking the buttons 1, then 2, then 3 in order should make the number "123" appear in the white text area. The C and CA keys should zero-out the values on the display.
Note that the new default application icon is in your Dock, shown on the right in Figure 5-26.
- Choose Calculator
Quit NewApplication to exit the Calculator application (we'll change the menu label from "NewApplication" to "Calculator" later).
Compiling and Running a Program in the Terminal Shell Window
localhost> cd ~/Calculatorlocalhost> pbxbuild
M-x compile <Return>
make
pbxbuild <Return>
localhost> open build/Calculator.appCompiler Error Messages
Sometimes (many times!) code does not compile properly, as you can see in the PB window in Figure 5-27.
Figure 5-27. Compiler errors in PB's main window
![]()
If instead of a clean compile you get compiler warning or error messages, you have probably made a typographical error at some point in the code. For example, the error message in Figure 5-27 was generated by removing the first semicolon from the displayX method in the Controller.m file (the semicolon is missing from the statement above the highlighted statement at the bottom in Figure 5-27).
If you click on an error message in the top-right panel of PB's window (e.g., "syntax error, found `setStringValue'"), the offending line of code will be highlighted in the source code file, Controller.m. If you double-click the error message, a new window will open with the line containing the error highlighted (actually, the error is in the previous line, but it doesn't cause a problem until the highlighted line). This is a great help in finding and fixing compiler errors!
If you get compiler errors for source code that you type in from this book, we suggest that you first reexamine your code line by line, rather than downloading our code from the Web. Examining code for errors is an important skill to develop.
As an alternative to double-clicking an error message, you can open the Controller.m file in a PB editor window, type Command-L to bring up the Goto panel, enter "45" (the line where the error was reported), and inspect the code on line 45 and previous lines.
(If you compiled your program from within GNU Emacs, you can use the Emacs command "goto-next-error" to automatically jump to the file and line containing the error.)
The enterDigit: Action Method
The enterDigit: method we added to the Controller class is invoked whenever a digit button is clicked. Let's look at it closely to see how it works.
- enterDigit:sender{if (enterFlag) {Y = X;X = 0.0;enterFlag = NO;}X = (X*10.0) + [ [sender selectedCell] tag];[self displayX];return self;}The first part of the function is self-explanatory: if the
enterFlaginstance variable is set, the value of the X register is copied into the Y register and both the X register andenterFlagare cleared. Note that the scope of instance variables such asenterFlagis the entire class definition. All methods within a class have access to all instance variables defined in that class.The next line contains the magic: the value in the X register is multiplied by 10 and added to the returned value, [[sender selectedCell] tag]. This performs a base-10 left-shift operation on X and then adds the last digit pressed. Let's look at this nested method expression in pieces.
[sender selectedCell] sends the selectedCell message to the variable
sender. When the enterDigit: method is invoked (called),senderis set to the id of the object that sent the message--in this case, the NSMatrix object. Clicking a button in an NSMatrix selects that button. Thus, the expression [sender selectedCell] returns the id of the NSButtonCell object for the button that was clicked. [[sender selectedCell] tag] then sends the tag message to the NSButtonCell object; this method asks the button for the tag of the cell. Thus, the nested message expression [[sender selectedCell] tag] returns the tag of the pressed button. (Recall that the tag on the digit button is equal to the digit label on the button.)Adding the Four Calculator Functions
We still need to add the functions that perform the calculations to our Calculator application. We'll add six new buttons in yet another NSMatrix. The Controller object will need to differentiate between the buttons somehow, so we'll assign them different integer tags.
Our first step in handling the four functions is to equip our Controller class with definitions for the mathematical operations that we want our Calculator to be able to handle. Then we'll add these functions to our Calculator's user interface.
- Using PB's (or another) editor, insert the following enumerated data type after the
#importdirective in the Controller.h file (remember that bold code should be typed into class files):
enum {PLUS = 1001,SUBTRACT = 1002,MULTIPLY = 1003,DIVIDE = 1004,EQUALS = 1005};These codes will correspond to the tags that we will give the arithmetic buttons in the NSMatrix (we don't want to confuse these tags with tags that we set previously). The Controller object will determine the tag of the button that sends it the action message and use that tag to figure out which function button the user has clicked. This is similar to what we did with the NSMatrix of digit buttons.
- Using an editor, insert the lines shown here in bold into the enterOp: method in the Controller.m file.
You may be able to use the same PB code pane (or separate window) that you used for Controller.h by pressing the up-down "stepper" arrows next to the filename and dragging to Controller.m. You can also type the three-key combination Command-Option-up-arrow to rapidly switch between a class's .h and .m files.
- (IBAction)enterOp:(id)sender{if (yFlag) { // Something is stored in Yswitch (operation) {case PLUS:X = Y + X;break;case SUBTRACT:X = Y - X;break;case MULTIPLY:X = Y * X;break;case DIVIDE:X = Y / X;break;}}Y = X;yFlag = YES;operation = [ [sender selectedCell] tag];enterFlag = YES;[self displayX];}The enterOp: method is the computational engine of our Calculator application. It performs the arithmetic operation that was stored in the
operationinstance variable, sets up the registers and flags for another operation or another button click, and then displays the contents of the X register in the window display area.- Activate IB and create an NSMatrix with six buttons, as shown in Figure 5-28. (If you don't remember how to do this, go back and review how the digit buttons were set up.)
Figure 5-28. Calculator window with operations in IB
![]()
As you try to place these buttons, you may want to resize or rearrange the existing buttons on the current Calculator interface. Feel free--one of the most powerful things about IB is that you move around an interface after you have created it.
- Set the title of each button to correspond with one of the six basic functions, as shown in Figure 5-28. You may want to use a larger font for some of the titles to make them more readable.
- Set the tag of each button (except "+/-") to correspond with the
enumdefined in the Controller.h file above.
To set the tag of a button, select the button, type Command-1 to display the NSButtonCell Info dialog, change the value of the tag, and press Return. Don't worry about the unary minus "+/-" button for now. Again, double-check your tags to make sure they are correct.
- Connect the new NSMatrix to the Controller by Control-dragging from the NSMatrix to the Controller instance icon and double-clicking the enterOp: action in the NSMatrix Info dialog. This connection is similar to the one we made for the numeric keypad.
- Choose IB's File
Save (or Save All) command to save the nib and Controller class files.
- Compile your program and run it. If you use PB, all you have to do is click the build and run button, and PB will do all the rest (including prompting for file saving).
All of the Calculator's buttons should now work properly, except for the unary minus button.
- Choose Calculator
Quit or click PB's Stop button to exit the Calculator application.
For easy access, we recommend that you keep the PB, IB, and Terminal application icons in your Dock while developing applications. Also, when you want to switch applications, use Mac OS X's Hide command rather than Quit. This keeps your screen clear and avoids the wait of having applications start up again when you need them.
Adding the Unary Minus Function to the Controller Class
We want the unary minus function (the button with the "+/-" on it) to change the sign of the number currently displayed in our Calculator's numeric display area. One way to implement this function is to handle it with another case in the
switchstatement in the enterOp: method--we could give the "+/-" key its own tag and have the enterOp: method intercept it and perform the appropriate function. The problem with this approach is that the unary minus function has little in common with the other arithmetic functions: it takes one argument instead of two, and it operates immediately on the displayed value. A far better way to implement this function is to implement a new action method in the Controller class.Using IB's Read Files Command with a New Action Method
Adding new action methods to existing classes is slightly more difficult than creating the initial class definition. Early versions of IB simply replaced the existing class files (Controller.h and Controller.m, here) with new versions, wiping out any source code that you might have added. Current versions of IB detect that you have made changes in the class files and allow you to merge the changes using the Merge Files application. Unfortunately, this can be a painful and error-prone process. The safest way to add new outlets and actions to files after they have been created and edited is to add these items directly to the Objective-C interface files in a text editor, and then use IB's Classes
Read Files menu command to inform IB about any new actions and outlets.
- Back in PB, insert the doUnaryMinus: action method definition, shown here in bold, into Controller.h. (You can tell that it's an action method because of its IBAction typing and because
senderis the only argument.)
...- (IBAction)clear:(id)sender;- (IBAction)clearAll:(id)sender;- (IBAction)enterDigit:(id)sender;- (IBAction)enterOp:(id)sender;- (void)displayX;- (IBAction)doUnaryMinus:(id)sender;@end- Now insert the doUnaryMinus: method shown here into Controller.m:
- (IBAction)doUnaryMinus:(id)sender{X = -X;[self displayX];}- Choose PB's File
Save All menu command to save the Controller class files.
It doesn't matter where you put this method in Controller.m, as long as it's between the directives
@implementationand@end. However, for consistency, we suggest that you order the method implementations in the same way that the method declarations are ordered in the Controller.h class interface file.Finally, we have to tell IB about the new doUnaryMinus: method and set up a connection between the on-screen unary minus button and the Controller:
- Activate IB by double-clicking MainMenu.nib in PB's main window (MainMenu.nib is one of the Resources in your project).
- Click on the Classes tab in IB's Nib File window to open the Classes pane.
- Select Controller in the class hierarchy in the Classes pane. Because it's a subclass of NSObject, you may have to scroll to the left (or use the Search field).
- Choose IB's Classes
Read Files menu command.[1]
IB will display the Read Files dialog, as shown in Figure 5-29.
Figure 5-29. Read Files dialog in IB
![]()
This dialog tells us that the definition for Controller.h will be parsed from the edited file on disk.
- Make sure that Controller.h is selected, as shown in Figure 5-29, and click Parse in IB's Read Files dialog to parse the saved Controller.h file on disk.
- Make sure that the Controller class is selected in the Nib File window and then type Command-1 to see the Controller's attributes in the Info dialog.
The new doUnaryMinus: action method should appear in the Controller Info dialog, as in Figure 5-30, indicating that the Read Files command worked.
Figure 5-30. New doUnaryMinus: action method in IB after Read Files command
![]()
- Double-click the unary minus ("+/-") button in the Calculator window. Make sure that the NSButton, not the NSMatrix as a whole, is selected.
- Connect the selected unary minus button to the Controller instance. Do this by Control-dragging from the button to the Controller instance in the Nib File window (note that the Instances tab view displays automatically) and then double-clicking the doUnaryMinus: action in the NSButtonCell Info dialog.
When a button in an NSMatrix object has its own target, as in the previous example, the button's target overrides the target of the NSMatrix. Thus, when the user clicks on the unary minus button, the button will send the doUnaryMinus: message to its own target, rather than sending the enterOp: message to the target of the NSMatrix.
- Back in PB, build and run the program (note how PB nicely prompts to save files).
The unary minus function should now behave as expected (although the application isn't perfect yet--it has some bugs).
You might be wondering why IB's Read Files command didn't bring in the definition of the displayX method in addition to the doUnaryMinus: method. The reason is that IB only looks for action methods when parsing a class interface (.h) file. An action method should always be declared in the form:
- (IBAction)methodname:(id)sender;with a single argument called
sender. As we'll see later, IB will also bring in outlet declarations when parsing a class interface file. These outlet declarations must be instance variables of the form:IBOutlet id outletname;Action methods and outlets are the only types of information that IB learns about a class when it parses a class interface file.
The Files in a Project
If you've been checking your ~/Calculator directory while stepping through this chapter, you'll probably have noticed that several files were automatically created in it. This section will discuss what these files contain and how they fit into a project.
PB's Groups & Files pane uses an outline view to list each project file by type, as shown in Figure 5-31. You can display the different types of files in this outline view by clicking the disclosure triangle next to a file type (e.g., Resources, Frameworks). In Table 5-1, we summarize what each file type means.
Figure 5-31. Groups & Files pane in Calculator's main window in PB
![]()
The main.m Program File Generated by PB
When you create a project, PB generates an Objective-C file called main.m containing the program's
main( )function. Themain( )function is where every Objective-C (and C) program begins. The following code in the main.m file is generated in the Calculator project:#import <Cocoa/Cocoa.h>int main(int argc, const char *argv[]){return NSApplicationMain(argc, argv);}As you can see, this is a very simple function! All it does is run the
NSApplicationMain( )function that is built into the Cocoa Application Kit framework.The Cocoa documentation for the NSApplication class (not function) defines the
NSApplicationMain( )function as the following:void NSApplicationMain(int argc, char *argv[]) {[NSApplication sharedApplication];[NSBundle loadNibNamed:@"myMain" owner:app];[NSApp run];}This function creates the NSApplication object, which creates the autorelease memory pool that we discussed in Chapter 4, then loads the application's main nib file (MainMenu.nib in Calculator) and starts the NSApplication object's main event loop. The main event loop handles menu clicks, keystrokes, and all of the other events to which an application can respond.
Control doesn't return to
main( )until the NSApplication object receives a stop: or terminate: message, which usually happens in response to a user's choosing the application's Quit menu item. At that point, themain( )function receives the return code from theNSApplicationMain( )function.Other PB-Generated Files
In addition to creating the Objective-C .h, .m, .nib, and main.m program files for a project, PB created the following files for us:
- Calculator.pbproj
- The directory containing the project files, which keep track of the individual parts of the project. You might investigate this directory in a Terminal window because, like Calculator.app, it appears to be a simple file (not a folder) in the Finder.
- Calculator.pbproj/project.pbxproj
- The project file that keeps track of the parts of the project.
- Calculator.pbproj/username.pbxuser
- The project file that keeps track of the preferences for the user username.
- English.lproj
- A directory that contains the information for an English-language version of our project, including the MainMenu.nib file (discussed earlier) in our example.
- English.lproj/InfoPlist.strings
- A string table that references all of the strings inside the English-language project.
Summary
We got a great start programming in this chapter! We started by building a real project with PB. Then we used IB and Objective-C to build user interface objects, create and customize our own class, and connect these user interface objects with an object of our new class. We also learned a little more about Objective-C and some AppKit classes, and a lot about the files that PB generates. In the process, we used four important operations that IB can perform on classes: Subclass, Instantiate, Read Files, and Create Files. These operations were all found in the Classes menu in IB (some of these operations can be performed in ways other than using the menu commands).
These operations are the basic building blocks that you will use to create your own applications, although the order in which you use them will vary from project to project. Typically, the steps for creating an application are the following:
- Create a project using PB.
- Build the application's user interface using IB.
- Customize the buttons and other user interface items in IB.
- Design the application's Controller class.
- Connect the controls to the Controller, and vice versa.
- Add code to make the Controller class work.
- Compile, test, and fix the code that you have created.
- Tweak the user interface as necessary.
In the next chapter, we'll add an About box (dialog) and some icons to our Calculator application, and we'll find out how to increase the efficiency of a Cocoa application by using separate nib files.
Exercises
- Instead of creating a doUnaryMinus: method to handle the "+/-" function key in Calculator, add the unary minus function to the enterOp: method.
- Instead of creating a single enterOp: method to handle the four arithmetic operations, create four separate methods to handle the four operations.
1. If you have the Controller class in IB's Classes tab selected, you will have an additional menu item labeled "Read Controller.h". As you might imagine, choosing this menu option causes the Controller.h file to be read directly, without forcing you to choose the file in the Read Files pane. This is a faster method of reading a class description.
Back to: Building Cocoa Applications: A Step-by-Step Guide
© 2001, O'Reilly & Associates, Inc.
webmaster@oreilly.com