Search the Catalog
Learning Cocoa

Learning Cocoa

By Apple Computer, Inc.
May 2001
0-596-00160-6, Order Number: 1606
370 pages, $34.95

Chapter 7
Currency Converter Tutorial

Contents:

Design the Currency Converter Application
Create the Currency Converter Project
Create the Currency Converter Interface
Define the Classes of Currency Converter
Connect ConverterControllerto the Interface
Implement the Classes ofCurrency Converter

In this chapter you'll build a single window application from beginning to end, giving you an opportunity to deepen your understanding of the Cocoa programming paradigms discussed in Chapter 6, "Essential CocoaParadigms". For the first time, you'll see the complete work flow typical of Cocoa application development:

  1. Design the application

  2. Create the project (Project Builder)

  3. Create the interface (Interface Builder)

  4. Define the classes (Interface Builder)

  5. Implement the classes (Project Builder)

  6. Build the project (Project Builder)

  7. Run and test the application

The application you'll build in this chapter is called Currency Converter--a simple utility that converts a dollar amount to an amount in another currency. Currency Converter is an extremely simple application, but there's still a design behind it. This design is based upon the Model-View-Controller (MVC) paradigm, the model behind many designs for object-oriented programs. MVC separates an application into different types of objects, each with specific roles and responsibilities. This design paradigm aids in the development of a maintainable, extensible, and reusable code base, as the Currency Converter example will make clear.

Design the Currency Converter Application

An object-oriented application should be based on a design that identifies the objects of the application and clearly defines their roles and responsibilities. You normally work on a design before you write a line of code. You don't need any fancy tools for designing many applications; a pencil and a pad of paper will do.

The Model-View-Controller (MVC) Paradigm

MVC proposes three types of objects in an application--model, view, and controller--as illustrated in Figure 7-1. In MVC designs, model objects hold data and define the logic that manipulates that data, view objects represent something visible on the user interface (a window or a button, for example), and controller objects act as a mediator between model objects and view objects.

Figure 7.1. The separation of model, view, and controller objects in an MVC design

The beauty of MVC is that the controller's central, mediating role frees the model objects from having to know about the state and events of the user interface, and that frees the view objects from having to know about the programmatic interfaces of the model objects.

MVC, strictly observed, is not advisable in all circumstances. Sometimes it's best to combine roles. For instance, in a graphics-intensive application, such as an arcade game, you might have several view objects that merge the roles of view and model. In some applications, especially simple ones, you can combine the roles of controller and model; these objects join the special data structures and logic of model objects with the controller's hooks to the interface.

MVC in Currency Converter's design

Currency Converter consists of two custom objects--Converter (model) and ConverterController (controller)--as well as a user interface (view) that is implemented using a collection of ready-made Application Kit objects. The converter object is responsible for computing a currency amount and returning that value. Between the user interface and the converter object is a controller object, ConverterController. ConverterController coordinates the activity between the converter object and the UI objects. These relationships are depicted in Figure 7-2.

Figure 7.2. MVC in Currency Converter's design

The ConverterController class assumes the central role in the application. Like all controller objects, it communicates with the interface and model objects, and it handles tasks specific to the application. ConverterController gets the values that users enter into fields, passes these values to the converter object, gets the result back from Converter, and puts this result in a field in the interface.

The Converter class merely computes a value from two arguments passed into it by the controller and returns the result. By insulating the Converter class from implementation-specific details of the user interface, the Converter class becomes easily reusable in other applications.

This design for Currency Converter is intended to illustrate specific aspects of object-oriented programming and MVC and so may be overdesigned for something so simple. It is quite possible to have the application's controller class, ConverterController, perform the computation and do without the Converter class. However, there is no harm in concentrating on good design from the start.

Create the Currency Converter Project

Now that you have a design for Currency Converter, it's time to start building the application.

Create the Application Project

Your first step toward completing the application is to create the Project Builder project.

  1. Start Project Builder.

  2. Choose New Project from the File menu.

  3. In the New Project panel, select the Cocoa Application project type and click the Next button.

  4. Name the application Currency Converter.

  5. If you wish, click Set to select a location to save the project in a specific location of your choice. To use the default location, go on to step 6.

  6. Click Finish.

Project Indexing

It's a good idea to have Project Builder index a project once it has been created. During indexing Project Builder stores all symbols of the project (classes, methods, globals, etc.) on disk. This allows Project Builder to access project-wide information quickly.

Note that the version of Project Builder included with the first release of Mac OS X contained a bug causing project indexing to fail unless the project had first been built. If you are working with this release, simply build the project before attempting to create a project index.

To create an index, choose Index Project (Command-Option-I) from the Project menu. Once your project has been indexed, you'll be able to use Project Find to search both your project's code and the system headers for symbols. You'll also be able to access reference documentation directly from Project Find results. Creating an index for a Cocoa project the first time will take a while because Project Builder indexes all of the Cocoa headers as well as the ones in your project.

Now that your project has been indexed, you can easily access documentation for a Cocoa class or method. For example, if you look inside the main.m file in your project's Other Sources group, you'll see a call to the function NSApplicationMain.

  1. Click the Find tab in Project Builder's main window. The Find pane will appear, as shown in Figure 7-3.

  2. Make sure that the Find Type pop up is set to Definitions.

  3. Type NSApplicationMain in the Find field and click the Find button.

Figure 7.3. Project Builder's Find panel

As you can see, Project Builder found one definition in NSApplication.h. If you click the header filename, Project Builder will show you the definition in the document pane. If you click on the book icon just to the left of the Find result, Project Builder will launch Help Viewer and allow you to access the reference documentation for this function. Note that Help Viewer may require you to navigate to the Cocoa area of the Developer Center before searches from Project Builder function correctly.

Create the Currency Converter Interface

Currency Converter's interface is really quite simple, consisting of a few text fields and a button. However, the process of creating it will give you an opportunity to explore some of the object layout tools available in Interface Builder that make it a joy to use.

Open the Main Nib File

Begin by creating an application's user interface in Interface Builder.

  1. Locate MainMenu.nib in the Resources group in Project Builder.

  2. Double-click to open it. This will open Interface Builder and bring up the nib file.

A default menu bar and window titled Window will appear when the nib file is open.

Resize the Window

In this section you'll change the size of the application's main window to accommodate the UI objects to be added.

  1. Using Figure 7-4 as a guide, make the window smaller by dragging an edge of the bottom-right corner of the window inward.

    Figure 7.4. Currency Converter's main window

    You can resize the window more precisely by using the Size menu of the Window Info window.

  2. Choose Show Info from the Tools menu.

  3. Select Size from the pop-up menu.

  4. In the Content Rect area, select Width/Height from the righthand pop-up menu. In the text fields under the Width/Height menu, type 400 in the width (w) field and 200 in the height (h) field as shown in Figure 7-5.

    Figure 7.5. The Window Info window

Set the Window's Title and Attributes

While the Info window is open, set other attributes for the window.

  1. Select Attributes from the Info window's pop-up menu and change the window's title to Currency Converter.

  2. Verify that the Visible at Launch Time option is selected.

  3. Deselect the Resize checkbox in the Controls area.

Place a Text Field; Resize and Initialize It

Currency Converter uses text fields to accept user input and display converted values.

  1. Drag a text field object onto the Currency Converter window as shown in Figure 7-6. Notice that Interface Builder helps you place objects according to the Aqua interface guidelines by displaying pop-up guides when an object is dragged close to the proper distance from neighboring objects or the edge of the window.

    Figure 7.6. Placing a text field

  2. Resize the text field by grabbing a handle and dragging in the direction you want it to grow. In this case, drag the left handle to the left to enlarge the text field, as shown in Figure 7-6.

Currency Converter needs two more text fields, both the same size as the first. You have two options: you can drag another object from the palette and make it the same size, or you can duplicate the first object.

Duplicate an Object

Using the text field you just added to the window as a template, duplicate it twice to create the remaining text field objects.

  1. Select the text field, if it is not already selected.

  2. Choose Duplicate (Command-D) from the Edit menu. The new text field appears slightly offset from the original field.

  3. Reposition the new text field under the first text field. Notice that guides appear and assist you by snapping the second text field into place.

  4. To make the third text field, type Command-D again. Notice that IB remembered the offset from the previous Duplicate command and automatically applied it to the newly created text field.

Change the Attributes of a Text Field

The bottom text field will display the results of the computation and should therefore have different attributes than the other text fields:

  1. Select the third text field.

  2. Bring up the Info window and choose Attributes from the pop-up menu.

  3. Turn off the Editable attribute in the Options section of the Inspector so that users will not be able to alter the contents of the field. Keep the Selectable attribute so that users can copy and paste the contents to other applications.

Assign Labels to the Fields

Text fields without labels would be confusing, so add labels by using the ready-made label object from the Views palette:

  1. Using Figure 7-7 as a guide, drag a Message Text object from the Views palette onto the window.

    Figure 7.7. Aligning a text field with its label

  2. Right-align the text; with the Message Text object selected, click on the third button from the left in the Alignment area of the Info window, as shown in Figure 7-8.

    Figure 7.8. The NSTextField Info window

  3. Duplicate the text label twice, enter the text for each, and align them, as shown in Figure 7-9.

    Figure 7.9. Aligning Currency Converter's text fields and labels

Add a Button to the Interface and Initialize It

The currency conversion can be invoked either by clicking a button or pressing Return:

  1. Drag the button object from the Views palette and put it in the lower-right portion of the window.

  2. Double-click the title of the button to select its text label and change the title to Convert.

  3. Choose Attributes in the NSButton Info window, and then choose Return from the pop-up menu labeled Equiv:. This will give the button the capacity to respond to the Return key as well as to mouse clicks.

  4. Align the button under the text fields. First, drag the button downward until the Aqua guide appears. With the button still selected, hold down the Option key and release the mouse button. If you move the cursor around, Interface Builder will show you the distance from the button to the object that you've indicated with the cursor. With the Option key still down and the cursor over the bottom text field, use the arrow keys to nudge the button to the exact center of the text fields as shown in Figure 7-10.

    Figure 7.10. Aligning the Convert button with the text fields

Add a Horizontal Decorative Line

You've probably noticed that the final interface for Currency Converter has a decorative line between the text fields and the button. To create this line:

  1. Drag a horizontal separator object from the Views palette onto the interface. It's located just beneath the Box object in the lower-righthand corner of the Views palette.

  2. Using Figure 7-11 as a guide, drag the endpoints of the line until the line extends across the window.

    Figure 7.11. Adding a decorative line to the interface

  3. Move the Convert button back up until the Aqua guide appears.

Aqua Layout and Object Alignment

In order to make an attractive user interface, you must be able to visually align interface objects in rows and columns. Eyeballing the alignments can be very difficult, and typing in x/y coordinates by hand is tedious and time consuming. Aligning Aqua interface widgets is made even more difficult because the objects have shadows and UI guideline metrics don't typically take the shadows into account. Interface Builder uses visual guides and layout rectangles to help you with object alignment.

In Cocoa, all drawing is done within the bounds of an object's frame. Because interface objects have shadows, they don't visually align correctly if you align the edges of the frames (as is done with Mac OS 9). For example, the Aqua UI guidelines say that a push button should be 20 pixels tall, but you actually need a frame of 32 pixels for both the button and its shadow. The layout rectangle is what you must align.

You can view the layout rectangles of objects in IB using Show Layout Rectangles in the Layout menu (Command-L). Also, the IB Size Inspector has a pop up to toggle between the frame and layout rectangle so you can set values by hand when appropriate.

Interface Builder gives you several ways to align objects in a window:

Look in the Alignment and Guides submenus of the Layout menu for various alignment commands and tools. You can also use the alignment tool (choose Alignment in the Tools menu), which provides a floating window with buttons that perform various types of alignment.

Center the Interface Objects and Resize the Window

Currency Converter's interface is almost complete. The finishing touch is to resize the window so that all of the objects are centered and properly aligned to each edge; currently the objects are aligned only to the top and right edges.

For Currency Converter, you will continue, using the automated Aqua guides and a few Layout commands:

  1. Select the third text label (Amount in Other Currency), then extend the selection (Shift-click) to include the other two.

  2. Resize all the labels to their minimum width by choosing Size to Fit in the Layout menu.

  3. Select Same Size from the Layout menu to make the selected text labels the same size.

  4. Drag the labels toward the left edge of the window and release them when the Aqua guide appears.

  5. Select all three text fields and drag them to the left, again using the guides to help you find the proper position.

  6. Shorten the horizontal separator and move the button into position again under the text fields.

  7. Resize the window using the guides to give you the proper distance from the text fields on the right and the Convert button on the bottom.

At this point, the application's window should look like Figure 7-18.

Figure 7.18. Final Currency Converter Interface

Enable Tabbing Between Text Fields

The final step in composing the Currency Converter interface has more to do with behavior than with appearance. You want the user to be able to tab from the first editable field to the second and back to the first. Many objects in Interface Builder's palettes have an instance variable named nextKeyView. This variable identifies the next object to receive keyboard events when the user presses the Tab key (or the previous object if Shift-Tab is pressed). If you want interfield tabbing, you must connect fields through the nextKeyView variable.

In order for interfield tabbing to start from the correct text field, you need to make the text field the application window's initialFirstResponder--the object in the window that will be first in line to accept events from the keyboard. You will learn more about FirstResponder and the Responder chain in Chapter 8, "Event Handling".

  1. In the Instances pane of the MainMenu.nib window, click on the Window instance and Control-drag a connection to the first text field in Currency Converter's window. Select the initialFirstResponder outlet as shown in Figure 7-19 and click Connect.

    Figure 7.19. Connecting Currency Converter's initialFirstResponder

  2. Select the first text field and Control-drag a connection line from it to the second text field as shown in Figure 7-20. Select nextKeyView and click Connect. The nextKeyView outlet identifies the next object to respond to events after the Tab key is pressed.

    Figure 7.20. Connecting text fields for inter-field tabbing

  3. Repeat the step, but connect the second field to the first.

Test the Interface

The Currency Converter interface is now complete. Interface Builder lets you test an interface without having to write one line of code:

  1. Choose FileSave All to save your work

  2. Choose FileTest Interface.

  3. Try various operations in the interface, such as tabbing and cutting and pasting between text fields.

  4. When finished, choose Quit New Application from the Interface Builder Application menu to exit test mode.

Notice that the screen position of the Currency Converter window in Interface Builder is used as the initial position for the window when the application is launched. Place the window near the top-left corner of the screen so it will be in a convenient (and traditional) initial location.

Define the Classes of Currency Converter

In this section you'll define controller and model classes for Currency Converter.

Create the ConverterController Subclass

You'll recall from Chapter 6, "Essential CocoaParadigms" that you must go to the Classes display of the nib file window to define a class. Let's start with the ConverterController class:

  1. In Interface Builder, select the Classes display of the MainMenu.nib window.

  2. Create an NSObject subclass called ConverterController.

Define Outlets for ConverterController

ConvertorController needs access to the text fields of the interface, so you must create outlets for them. ConverterController must also communicate with the Converter class (yet to be defined) and thus requires a fourth outlet for that purpose.

  1. Select ConverterController in the Classes window.

  2. Click the electrical outlet icon to the right of the class.

  3. Choose Add Outlet from the Classes menu.

  4. Name this outlet rateField and press Return.

  5. Since the rateField outlet is still selected, all you have to do to create more outlets is press Return. Do this once to create the dollarField outlet, and again for the totalField outlet.

  6. Add an outlet named converter to ConverterController.

Define Actions for ConverterController

ConverterController has one action method, convert:. When the user clicks the Convert button, a convert: message is sent to the target object, an instance of ConverterController. Action refers both to a message sent to an object when the user clicks a button or manipulates some other control object and to the method that is invoked.

  1. Choose Add Action from the Classes menu.

  2. Type the name of the method, convert. IB adds the : for you.

Define the Converter Class

The Converter class doesn't need to inherit any special functionality, so you'll make it a subclass of NSObject. Because instances of this model class don't communicate directly with the interface, there is no need for outlets or actions.

  1. In the Classes display, make Converter a subclass of NSObject.

  2. Save MainMenu.nib.

Connect ConverterControllerto the Interface

In this section you'll create an instance of ConverterController and use Interface Builder to connect the controller object's outlets to objects in the user interface.

Generate an Instance of the Class

As the final step of defining a class in Interface Builder, create an instance of your class and connect its outlets and actions:

  1. Select ConverterController in the Classes window, if it is not already selected.

  2. Choose Instantiate from the Classes menu. The instance will appear in the Instances view, as highlighted in Figure 7-21.

    Figure 7.21. The ConverterController instance

Connect the Custom Class to the Interface

Now you can connect this ConverterController object to the user interface. By connecting it to specific objects in the interface, you initialize its outlets. ConverterController will use these outlets to get and set values in the interface.

  1. In the Instances display of the nib file window, Control-drag a connection line from the ConverterController instance to the first text field, as shown in Figure 7-22. When the text field is outlined, release the mouse button.

    Figure 7.22. Connecting the ConverterController instance to a text field

  2. Interface Builder brings up the Connections display of the Info window. Select the outlet that corresponds to the first field (rateField).

  3. Click the Connect button.

  4. Following the same steps, connect ConverterController's dollarField and totalField outlets to the appropriate text fields.

Connect the Interface Controls to the Class's Actions

You must connect the Convert button to its action method in Interface Builder so that the button will send the message to the controller object at runtime.

  1. Control-drag a connection from the Convert button to the ConverterController instance in the nib file window. When the instance is outlined, release the mouse button.

  2. In the Connections display, make sure target in the Outlets column is selected.

  3. Select convert: in the Actions column.

  4. Click the Connect button.

  5. Save the MainMenu.nib file.

Connect ConverterController to the Converter Class

While connecting ConverterController's outlets, you probably noticed that one outlet--converter--remains unconnected. This outlet identifies an instance of the Converter class in the Currency Converter application, but this instance doesn't exist yet.

  1. Instantiate the Converter class.

  2. Make an outlet connection between ConverterController and Converter. Hint: Control-drag from the ConverterController instance to the Converter instance.

  3. Save MainMenu.nib.

Implement the Classes ofCurrency Converter

The final step in building the Currency Converter application is to implement the classes you defined in the previous steps.

Generate the Source Files

  1. Go to the Classes display of the nib file window.

  2. Select the ConverterController class.

  3. Choose Create Files from the Classes menu.

  4. Verify that the checkboxes in the Create column next to the .h and .m files are selected.

  5. Verify that the checkbox next to Currency Converter is selected.

  6. Click the Choose button.

  7. Repeat for the Converter class.

  8. Save the nib file.

Now we leave Interface Builder for this application. You'll complete the application using Project Builder.

Examine an Interface (Header) Filein Project Builder

When Interface Builder adds the header and source files to the Currency Converter project, it tries to put them in the same group folder as other source files in the same disk folder. Since the newly created files are class implementations, move them to the Classes group if Interface Builder did not do so automatically.

  1. Click Project Builder's main window to activate it.

  2. Select all four files in the Groups and Files list and drag them into the Classes group, as shown in Figure 7-23.

    Figure 7.23. Adding the source files to the Classes group

Add a Method Declaration

You can add instance variables or method declarations to a header file generated by Interface Builder. This is commonly done, but it isn't necessary in ConverterController's case. But we do need to add a method to the Converter class that the ConverterController object can invoke to get the result of the computation. Let's start by declaring the method in Converter.h.

  1. Select Converter.h in the project browser.

  2. Insert a declaration for convertAmount:atRate::

    #import <Cocoa/Cocoa.h>
    @interface Converter:NSObject
    {
    }
    - (float)convertAmount:(float)amt atRate:(float)rate; 
    
    @end

This declaration states that convertAmount:atRate: takes two arguments of type float and returns a float value. When parts of a method name have colons, such as convertAmount: and atRate:, they are keywords that introduce arguments.

Now you need to update both implementation files.

Implement Currency Converter's Classes

For the Converter class, implement the method you just declared in Converter.h. Method implementations go between @implementation <class name> and @end, so this is where you will add the code for Converter.

  1. Select Converter.m from the Classes group in Project Builder's main window.

  2. Insert the code for convertAmount::

    #import "Converter.h"
    @implementation Converter
    - (float)convertAmount:(float)amt atRate:(float)rate
    {
        return (amt * rate); 
    }
    @end

    The method simply multiplies the two arguments and returns the result. Simple enough.

  3. Next, update the "empty" implementation of the convert: method in ConverterController.m that Interface Builder generated for you:

    - (IBAction)convert:(id)sender
    {
        float rate, amt, total;
    
        amt = [dollarField floatValue];
        rate = [rateField floatValue];
    
        total = [converter convertAmount:amt atRate:rate];
    
        [totalField setFloatValue:total];
        [rateField selectText:self];
    }
  4. Make sure that ConverterController.m imports Converter.h by adding the following line at the top of the source file:

    #import "Converter.h."

The convert: method does the following:

Build and Run Currency Converter

This section explains how to build and run the application.

Build the project

You begin the build process from the Project Build panel:

  1. Save source code files and any changes to the project.

  2. Click the Build button on the main window.

When you click the Build button, the build process begins. When Project Builder finishes, and encounters no errors along the way, it displays Build Succeeded in the lower-left corner of the project window.

Run Currency Converter

Enter some rates and dollar amounts and click Convert. Also, select the text in a field and choose Services from the Application menu; this menu now lists the other applications that can do something with the selected text.

Of course, the more complex an application is, the more thoroughly you will need to test it. You might discover errors or shortcomings that necessitate a change in overall design, in the interface, in a custom class definition, or in the implementation of methods and functions.

Although it's a simple application, Currency Converter consolidates all of the concepts and techniques introduced in previous chapters. By now you have a much better grasp of the skills you'll need to develop Cocoa applications. To review, you have learned to:

Back to: Sample Chapter Index

Back to: Learning Cocoa


O'Reilly Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies

© 2001, O'Reilly & Associates, Inc.
webmaster@oreilly.com