Chapter 5. Graphical User Interfaces

Now that we’ve covered the Foundation, we’re going to take a step up and start working with the AppKit framework to create GUI-based applications. In this chapter, we’ll build a single-window application from beginning to end, letting us introduce the various GUI subjects necessary to become proficient with Cocoa programming. For the first time, you’ll see the complete workflow typical of Cocoa application development, composed of the following steps:

  1. Design the application.

  2. Create the project using Project Builder.

  3. Create the interface using Interface Builder.

  4. Define the classes using Interface Builder.

  5. Connect the Model, View, and Controller objects using Interface Builder.

  6. Implement the classes using Project Builder.

  7. Build and run the project using Project Builder.

The application we’ll build in this chapter is a currency converter—a simple utility that converts a dollar amount to an amount in some other currency. This example has been one of the mainstay examples of NeXTSTEP/OpenStep/Cocoa programming; it’s been around almost long enough to reach “Hello World” status. Although it is a simple application, it consolidates quite a few of the concepts and techniques needed to get started with writing Cocoa GUI applications.

After working through this first complete GUI application, we’ll spend the rest of this section of the book exploring in-depth the topics introduced in this chapter.

Graphical User Interfaces in Cocoa

Graphical user interfaces in Cocoa are built on the following four concepts:

  • Windows

  • Nib files

  • Outlets

  • Actions

Windows

A window in Cocoa looks similar to windows in other user environments, such as Microsoft Windows or earlier versions of the Mac OS. A window can be moved around the screen and stacked on top of other windows like pieces of paper. A typical Cocoa window, shown in Figure 5-1, has a titlebar, content area, and several control objects.

A typical Cocoa-based window
Figure 5-1. A typical Cocoa-based window

Many user-interface objects other than the standard windows are window objects without the standard window widgets. These include menus, pop-up menus, dialog boxes, sheets, alerts, panels, info windows, tool tips, tool palettes, and scrolling lists. In fact, anything drawn on the screen must appear in a window. End users, however, may not recognize or refer to them as “windows.”

Nib Files

A nib file is an archive of object instances generated by Interface Builder. Unlike the product of many user interface-building systems, a nib file is not generated code. It is a set of true objects that have been encoded specially and stored on disk. The objects in the nib file are created and manipulated using Interface Builder’s graphical tools.

Nib files typically package a group of related user-interface objects and supporting resources, along with information about how the objects are related—both to one another and to other objects in your application. Nib files hold all of the objects they describe by specially archiving, or freeze-drying, so that they can be reconstituted in a running application and then used again.

Every application with a graphical user interface has at least one nib file that is loaded automatically when the application is launched. The main nib file typically contains the application menu. Auxiliary nib files contain the application windows, as well as their associated user-interface objects. For example, an image-manipulation program such as Photoshop might have auxiliary nib files for the various palettes and controls that let you work with an image.

It can be useful to think of the objects that compose a user interface, and are contained within a nib file, as forming a hierarchy. Figure 5-2 shows the ownership hierarchy of nib-based objects for Figure 5-1. At the top of the nib file’s hierarchy of archived objects is the file’s owner object, a proxy object pointing to the actual object that owns, or controls, the nib file—typically the object that loaded the nib file from disk.

Ownership hierarchy of nib-based objects for Figure 5-1
Figure 5-2. Ownership hierarchy of nib-based objects for Figure 5-1

Outlets

An outlet is a special-instance variable, marked with the IBOutlet keyword in a class’s header, that contains a reference to another object, as shown in Figure 5-3. An object can communicate with other objects in an application by sending messages to them through outlets.

Object connected to a text field via an outlet
Figure 5-3. Object connected to a text field via an outlet

An outlet can reference any object in an application: user-interface objects, instances of custom classes, and even the application object itself. What distinguishes outlets from other instance variables is that Interface Builder recognizes the IBOutlet keyword and lets you manipulate the connections it defines. These connections, once defined, will be linked up for you when your application runs. Specifying these relationships between objects in Interface Builder saves you from having to write initialization code by hand. There are ways other than outlets to reference objects in an application, but outlets and Interface Builder’s facility for initializing them are a great convenience.

Actions

Actions are special methods, indicated with the IBAction keyword, which are defined by a class and triggered by user-interface objects. Interface Builder recognizes action declarations in a header file, as it does with outlets. Similarly, Interface Builder allows you to connect actions that a user might take with an interface, such as pushing a button, to methods on an object. These connections are shown in Figure 5-4.

Targets and actions
Figure 5-4. Targets and actions

An action refers both to a message sent to an object when the user clicks a button (or manipulates some other control), and to the invoked method.

Designing Applications Using MVC

Cocoa applications make use of a long-standing object-oriented paradigm called Model-View-Controller (MVC). As illustrated in Figure 5-5, MVC proposes three types of objects in an application—model, view, and controller:

Model

Objects that hold data and define the logic that manipulates that data

View

Objects that represent something visible to the user, such as a window or a button

Controller

Objects that act as mediators between model and view objects

Model, view, and controller objects in an MVC design
Figure 5-5. Model, view, and controller objects in an MVC design

The MVC paradigm works well for many applications, because the controller’s central and mediating role frees the model objects from needing to know about the state and events of the user interface. Likewise, the view objects don’t have to know about the programmatic interfaces of the model objects. Dividing the problem along these lines helps encapsulate the various objects in an application. This can also aid reuse, since the model could be used elsewhere, perhaps even on another platform.

MVC, strictly observed, is not advisable in all circumstances. Sometimes it can be advantageous to combine roles. For example, in an graphics-intensive application, such as an arcade game, you might have several view objects that merge the roles of view and model for performance reasons. In other 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

The Currency Converter application will consist of two custom objects: a Converter that will serve as our model and a Controller that will mediate between the user interface and the Converter object. We’ll create the view of the application using a collection of AppKit objects, which we’ll assemble using Interface Builder. The relationships between these objects are shown in Figure 5-6.

MVC in Currency Converter’s design
Figure 5-6. MVC in Currency Converter’s design

The Controller object will assume 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. The Controller object gets the values that users enter into fields, passes these values to the Converter object, gets the result back from the Converter, and puts this result into a field in the interface. By insulating the Converter from the implementation-specific details of the user interface, the Converter object becomes a reusable component for other applications.

Create the Currency Converter Project

Now that we have designed the application, we can get to work on the implementation. In Project Builder, create a new Cocoa Application project (File New Project Application Cocoa Application) as shown in Figure 5-7, then name the project “Currency Converter”, and save it in your ~/LearningCocoa folder.

If you click on the disclosure triangle next to Other Sources in the left pane and click on main.m, you’ll notice that the file looks a bit different from the Foundation projects we’ve worked with in the past. The main.m file contains the following code:

//
//  main.m
//  Currency Converter
//
//  Created by James Duncan Davidson on Fri Aug 30 2002.
//  Copyright (c) 2002 __MyCompanyName__. All rights reserved.
//

#import <Cocoa/Cocoa.h>

int main(int argc, const char *argv[])
{
    return NSApplicationMain(argc, argv);
}
Creating a new Cocoa Application
Figure 5-7. Creating a new Cocoa Application

Notice that the import statement has changed to importing Cocoa.h instead of Foundation.h. The Cocoa.h header contains the definitions for both the Foundation and AppKit classes. Also notice that the main method makes a call to the NSApplicationMain function. This function is defined by the AppKit and will start the application, load the main nib file, and set up the event loop and autorelease pool for that loop. Now that we have taken a look at this source file, we can let it be. You’ll very rarely, if ever, modify the main.m file of a Cocoa GUI application.

Tip

Project Builder automatically generates the comments at the top of the source file from the Cocoa Application template. You’ll probably want to change the __MyCompanyName__ text to the actual copyright holder and make sure that the copyright year is correct. See Chapter 17 for more details on how to finish and polish your applications.

Create the Interface

The Currency Converter’s interface is actually quite simple to create. It consists of a few text fields and a button. The process of creating it will give you an opportunity to explore how Interface Builder works. Figure 5-8 shows a hand-drawn sketch of how we’d like the interface to look. This gives us something to go by when designing the interface in Interface Builder.

The Currency Converter interface
Figure 5-8. The Currency Converter interface

Open the Main Nib File

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

  1. In Project Builder’s left pane, click on the disclosure triangle next to Resources to reveal the MainMenu.nib file.

  2. Double-click on MainMenu.nib to open it in Interface Builder.

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

Resize the Window

The window is a bit large for our purposes. You can change the size either by dragging the bottom-right corner of the window or by using the Info window, as shown in Figure 5-9. You can open this window by selecting Tools Show Info from Interface Builder’s menu (Shift-

image with no caption

-I).

Info window showing the Size panel
Figure 5-9. Info window showing the Size panel

When you have opened the Info window, use the following process to resize the window:

  1. Select Size from the Info window’s pop-up menu.

  2. In the Content Rect area, select Width/Height from the right-hand pop-up menu.

  3. 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 5-9.

Set the Window’s Title and Attributes

By default, our window has a title of Window. We want the application window to have a more meaningful title, as well as a few other attributes that we care about.

  1. Select Attributes from the Info window’s pop-up menu, and change the window’s title to Currency Converter, as shown in Figure 5-10.

  2. Verify that the Visible at launch time option is selected. This will ensure that this window is created on screen when the application is launched.

  3. Deselect the Resize checkbox in the Controls area. This will prevent users from resizing the application.

Info window showing the Attributes panel
Figure 5-10. Info window showing the Attributes panel

Place the Text Fields

The Currency Converter will use text fields to accept user input and display converted values. To place a text field into the window:

  1. Drag an NSTextField object from the Views palette (shown in Figure 5-11), and place it in the upper-right corner of the application window.

    When you drag the text field onto the window, Interface Builder helps you place objects according to the Aqua Human Interface Guidelines (HIG) by displaying guidelines when an object is dragged close to the proper distance from neighboring objects or the edge of the window.

    Dragging an NSTextField from the Views palette
    Figure 5-11. Dragging an NSTextField from the Views palette
  2. Resize the text field by grabbing a handle and dragging it in the direction in which you want it to grow. In this case, drag the left handle to the left to enlarge the text field, as shown in Figure 5-12.

    Resizing a text field
    Figure 5-12. Resizing a text field

    Just as you can specify the size of the application window, you can also specify exact sizes for other elements of your application. For example, if you want the text field to be 150 pixels wide, select the NSTextField object, and then select Size from the NSTextField Info window (Shift-

    image with no caption

    -I). In the width field (w), enter 150 as the value, and hit the Tab key to accept the value; the NSTextField object will conform to its newly defined dimensions.

Duplicating Objects

Currency Converter needs two more text fields, each the same size as the first. To place these fields, you have two options: you can drag another text field from the palette and make it the same size, or you can duplicate the first object. To create a new text field by duplication:

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

  2. Choose Edit Duplicate (or use the keyboard shortcut,

    image with no caption

    -D). The new text field appears slightly offset from the original field.

    Another way to duplicate a field is to click on the object, then hold down the Option and drag the object. A plus sign will appear next to the pointer to indicate that you’re making a copy of the object, and the guidelines will help you move the newly duplicated object into place.

  3. Reposition the new text field under the first text field. You’ll notice that the guides will appear once again to help you move the second text field into place.

  4. To make the third text field, make another duplicate. Notice that Interface Builder remembers the offset from the previous Duplicate command and automatically uses that offset to create and place the third text field.

Change the Attributes of a Text Field

Since the third text field will display the results of the computation, it should not be editable. To change its attributes:

  1. Select the third text field.

  2. Choose Attributes from the Info window’s pop-up menu, as shown in Figure 5-13.

  3. In the Options section of the Info window, uncheck the Editable attribute so users cannot alter the contents of the field.

  4. Make sure that the Selectable attribute is on so that users can copy and paste the contents of this field to other applications.

The NSTextField Info window
Figure 5-13. The NSTextField Info window

Add Text Labels

Next we need to add some labels to the text fields, so the user will know why the fields are there.

  1. Using Figure 5-14 as a guide, drag a System Font Text object from the Views palette onto the Currency Converter window.

    Aligning a text field with its label
    Figure 5-14. Aligning a text field with its label
  2. Right-align the text using the Info window.

  3. Duplicate the text label twice, and then edit the text for all three labels, as shown in Figure 5-15. To edit a text label, double-click on the current label (System Font Text) to highlight it, then type in the new label. After entering the new label, hit the Tab key to accept the new label.

    Tip

    Hitting the Return key will not accept the new label; instead, it will insert a carriage return.

    Currency Converter’s text fields and labels
    Figure 5-15. Currency Converter’s text fields and labels

    As you type in the new labels, you’ll notice that the text fields aren’t wide enough to hold the text shown in Figure 5-15. To correct this problem, resize the text fields by grabbing the middle-left field holder and dragging the edge of the text field to the left until all of the text appears.

Add a Button to the Interface

The last functional part of the user interface that we need to add is the Convert button. It needs to be set up so that it can be invoked either by clicking it or by pressing Return when the application has the user’s focus.

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

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

  3. With the button selected, choose Attributes in the Info window, and then choose Return from the pop-up menu labeled Equiv, as shown in Figure 5-16. This allows the button to respond to the Return key, as well as to mouse clicks.

    The NSButton Info window, used to change the Convert button’s attributes, so it will respond to a mouse-click or to the Return key
    Figure 5-16. The NSButton Info window, used to change the Convert button’s attributes, so it will respond to a mouse-click or to the Return key
  4. Align the button under the text fields. To center the button under the text fields, you can pop up a set of measurement guides that tell you the distance from an object to any of its neighboring objects. With the button selected, hold down the Option key and point to an object whose distance from the button you want to see, as shown in Figure 5-17. With the Option key still down, use the arrow keys to nudge the button to the exact center of the text fields.

    Aligning the Convert button with the text fields
    Figure 5-17. Aligning the Convert button with the text fields

Adding a Decorative Line

Lines can separate elements to help the user make sense of the objects in the user interface. We’ll add a line between the editable fields and the result field.

  1. Move the third text field and label down a bit in the user interface.

  2. Drag a horizontal line from the Views palette onto the interface, and use the alignment guides to place it right under the dollars text field.

  3. Use the selection handles on the line to extend it to each side of the interface.

  4. Move the result text field and label back up into position using the guides, then move the Convert button up into place.

  5. 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, Currency Converter’s application window should look like Figure 5-18.

The final Currency Converter interface
Figure 5-18. The final Currency Converter interface

Setting the Initial First Responder and Enabling Tabbing

The final step in composing Currency Converter’s interface has little to do with appearance and everything to do with behavior. When users launch the application, they should immediately be able to enter information in the interface and tab between the text fields.

The first place a user’s input should go when they launch the application is in the first text field. To ensure that this happens, specify the first text field as the application window’s initial first responder -- the object in the window that will be first in line to accept events from the keyboard. To do this:

  1. In the Instances pane of nib file window, click on the Window instance and Control-drag a connection to the first text field in Currency Converter’s window.

  2. Select the initialFirstResponder outlet in the Info window, as shown in Figure 5-19, and click the Connect button.

    Connecting Currency Converter’s initial first responder
    Figure 5-19. Connecting Currency Converter’s initial first responder

Next, we want to ensure that when the user presses the Tab key, the focus moves to another text field. To do this:

  1. Select the first text field, and Control-drag a connection line from it to the second text field, as shown in Figure 5-20.

    Connecting text fields for inter-field tabbing
    Figure 5-20. Connecting text fields for inter-field tabbing
  2. Select the nextKeyView outlet in the Info window, and click the Connect button.

  3. Repeat the previous two steps, but connect the second field to the first. This will make it so you can tab from the second field back up to the first text field.

Test the Interface

The Currency Converter interface is now complete. Interface Builder lets you test the interface without having to write any code.

  1. Choose File Save All to save your work.

  2. Choose File Test Interface (

    image with no caption

    -R) to launch the interface in a mode where you can test it.

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

  4. When finished, choose Quit New Application (

    image with no caption

    -Q) from the Interface Builder application menu to exit the text 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 that it will be a convenient (and traditional) initial location.

Define the Classes

We’ll define the two classes needed for our application here in Interface Builder: a controller class and a model class. If you recall, the controller class controls the interaction between the model and view objects, while the model object holds data and defines the logic that manipulates that data.

Create the Controller Class

The controller class, Controller, doesn’t need to inherit any special functionality from other classes, so it will be a subclass of NSObject. To define it:

  1. Click the Classes tab of the MainMenu.nib window, as shown in Figure 5-21.

  2. Select NSObject from the list of classes.

  3. Press Return to create a new subclass of NSObject, and rename it Controller.

Creating the Controller class and defining outlets
Figure 5-21. Creating the Controller class and defining outlets

Define outlets for the controller

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

  1. Select the Controller class in the Classes window, as shown in Figure 5-21.

  2. Select the Attributes menu item in the Info window.

  3. Add an outlet named rateField by clicking the Add button, entering the name, and pressing Return.

  4. Create three more outlets, named dollarField, totalField, and converter, as detailed in step 3.

Define actions for the controller

The Controller class needs only one action method to respond to user-interface events. When the user clicks the Convert button (or uses the Return key, which we defined as an equivalent), we want a convert: message sent to an instance of the Controller.

  1. Click on the Action tab in the Info window.

  2. Add an action named "convert:". Interface Builder will add the ":" for you if you don’t.

Define the Model Class

Like the Controller class, the Converter class—our model in MVC speak—doesn’t need to inherit any special functionality, so you can make it a subclass of NSObject. Because instances of this class won’t communicate directly with the interface, there’s no need for outlets or actions.

  1. In the Classes window, create the Converter object, and make it a subclass of NSObject by clicking on NSObject, hitting Return, and entering Converter as the subclass’s name.

  2. Save MainMenu.nib. As with any program, it’s always a good idea to hit

    image with no caption

    -S every now and then, so you won’t lose your work.

Connect the Model, Controller, and View

The last task that remains in Interface Builder is to hook up the various parts of our application so that each part can talk to the others.

Generate an Instance of the Controller and Model

When the application is first launched and the nib file is loaded, we want to create an instance of both our controller and model classes. To do this:

  1. Select Controller in the Classes tab of the nib file window.

  2. Choose Instantiate from the Classes menu. The instance will appear in the Instances view of the MainMenu.nib window, as shown in Figure 5-22.

  3. Repeat the process for the Converter class.

The Converter and Controller instances
Figure 5-22. The Converter and Controller instances

Connect the Controller to the Interface

Now you can connect the Controller instance object to the user interface. By connecting it to specific objects in the interface, you initialize its outlets. Controller 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 Controller instance to the first text field, as shown in Figure 5-23.

    Connecting the Controller instance to the rate text field
    Figure 5-23. Connecting the Controller instance to the rate text field
  2. Interface Builder will bring up the Connections display of the Info window. Select the action that corresponds to the first field, rateField.

  3. Click the Connect button.

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

To tell the controller that it is time to perform an action, we need to hook up the Convert button to the Controller.

  1. Control-drag a connection from the Convert button to the Controller instance in the nib file window. Instead of dragging from the controller object to an interface object, we are dragging a connection from a user-interface object to the controller.

  2. In the Connections Info window, make sure that the target is selected in the Outlets column, as shown in Figure 5-24.

    Connecting the Convert button to the Controller
    Figure 5-24. Connecting the Convert button to the Controller
  3. Select convert: in the Actions column.

  4. Click the Connect button.

Connect the Model to the Controller

The last connection is to hook up the instance of our Converter model class to the Controller.

  1. In the Instances view of the nib file window, Control-drag the Controller instance to the Converter instance.

  2. Connect the Converter instance to the converter outlet.

Implement the Classes

Now we come to the part of this exercise where we take all of that work done in Interface Builder, generate the source files for our classes, and finish the class implementations in Project Builder.

Generate the Source Files

To generate the source files, follow these steps:

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

  2. Select the Controller 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. Click the Choose button.

  6. Repeat Steps 1-5 for the Converter class.

  7. Save the nib file.

Tip

You can also create the files for a class by Control-clicking (or right-clicking if you have a two-button mouse) on the class name in the Classes menu and selecting the “Create files for...” menu item.

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

Examine an Interface (Header) File in 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 the Controller and Converter files in the Groups & Files list, and drag them into the Classes group, as shown in Figure 5-25.

Adding the source files to the Classes group
Figure 5-25. Adding the source files to the Classes group

Look at the Controller.h file that Interface Builder generated. Notice that in addition to being declared of type id, our variables have an IBOutlet declaration. This is a macro that, in the compiler, doesn’t evaluate anything. It is used as a hint to Interface Builder’s parser, telling it that the variable is an outlet. You will also notice that the convert: method has a return type of IBAction. This type is the same as void and also tells Interface Builder that the method serves as an action that can be hooked up to user-interface elements and other objects. These declarations allow you to add outlets and actions in the code and enable Interface Builder to parse them. We’ll see this in action in later chapters.

Add the Conversion Method

We need to add a method to the Converter class that the controller object can invoke to perform our currency conversion.

  1. Start by declaring the convertAmount:atRate: method in Converter.h, as shown in Example 5-1. This method declaration states that convertAmount:atRate: takes two arguments of type float and returns a float value.

    Example 5-1. Converter.h header file
    #import <Cocoa/Cocoa.h>
    @interface Converter : NSObject
    {
    }
    - (float)convertAmount:(float)amt atRate:(float)rate;
    @end
  2. Add the method implementation to the Converter.m file, as shown in Example 5-2. This method simply multiplies the two arguments and returns the result.

    Example 5-2. Converter.m implementation file
    #import "Converter.h"
    
    @implementation Converter
    - (float)convertAmount:(float)amt atRate:(float)rate
    {
        return (amt * rate);
    }
    @end
  3. Update the “empty” implementation of the convert: method in Controller.m that Interface Builder generated for you, as shown in Example 5-3.

    Example 5-3. Controller.m implementation file
    #import "Controller.h"
    #import "Converter.h"                                                 // a
    
    @implementation Controller
    
    - (IBAction)convert:(id)sender
    {
        float rate = [rateField floatValue];                              // b
                                   float amt = [dollarField floatValue];                             // c
                                   float total = [converter convertAmount:amt atRate:rate];          // d
                                   [totalField setFloatValue:total];                                 // e
    }
    
    @end

    The lines we added do the following things:

    1. Imports the Converter class interface.

    2. Gets the value of the rateField outlet of the interface as a floating-point number. All text fields (and other classes that inherit from NSControl) can present the data that they contain in various forms, including doubles, floats, Strings, and integers.

    3. Gets the value of the dollarField outlet of the interface as a floating-point number.

    4. Calls the convertAmount:atRate: method of the Converter object instance.

    5. Sets the value of the totalField outlet of the interface to the result obtained from the Converter object instance.

Build and Run

When you click the Build and Run button, the build process begins. When Project Builder finishes—and hopefully encounters no errors along the way—it displays Build succeeded on its status line and starts the application.

To exercise the application, enter some rates and dollar amounts, and click Convert. 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.

Exercises

  1. Change the font used by the text labels on the application to Helvetica.

  2. Change the color of text displayed in the totalField to blue.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset