Tiny.m Revisited

Now let’s take another look at Tiny.m . Here is the start of the Tiny.m program:

/* Tiny.m
 * A tiny Cocoa application that creates a window
 * and then displays graphics in it.
 */

Like any well-written program, Tiny.m begins with a set of comments describing what the program does. Objective-C supports the standard ANSI C style of comments. That means that anything enclosed between a /* and a */ is a comment. Anything on a line following a double forward slash (// ) is a comment as well. Thus:

/* This is a comment */
// This is a comment as well

The next line of Tiny.m imports the Cocoa header files for the Foundation and Application Kit frameworks:

#import <Cocoa/Cocoa.h>

This statement brings in the Objective-C class definitions for the entire Cocoa framework, including the Foundation and the Application Kit. Recall from earlier chapters that the Foundation is a collection of tremendously useful classes for managing strings, arrays, queues, and other traditional data structures. The Application Kit is the collection of classes that are used to display the graphical user interface; often called the AppKit, this framework includes the fundamental NSApplication, NSWindow, and NSView classes.

Tip

You might think that importing such a large number of files would slow down the compilation process. In fact, it does not, because all of the Cocoa headers are precompiled. As long as you #import <Cocoa/Cocoa.h> before you do anything else in your program, the required time is practically nil.

Every Cocoa program has one, and only one, instance of the NSApplication class. It’s usually created inside a function called NSApplicationMain( ) by sending sharedApplication messages to the NSApplication class. In our example, we will create it in the function called main( ).

The NSApplication object is the most crucial object in the program because it provides the framework for program execution. The NSApplication class connects the program to the Window Server, initializes the Quartz display environment for this application, and maintains a list of all of the application’s windows. The NSApplication object receives events such as keypresses and mouseclicks from the Window Server and distributes them to the proper NSWindow objects, which in turn distribute the events to the proper objects inside the on-screen windows.

The NSWindow class is where the master control of your program’s on-screen windows is defined. For every window that your program displays, there is an associated instance (object) of the NSWindow class inside the computer’s memory. You can send messages to NSWindow objects that make the associated on-screen windows move, resize, reorder to the top of the window display list (placing themselves on top of the other windows), and perform many other operations.

The NSView class is the class that plays the most central visual role in Cocoa applications. Many of the classes in the AppKit inherit from the NSView class. NSView objects are responsible for drawing in windows and receiving events. Each NSView object can contain any number of NSView objects, called subviews . When a window receives a mouse event, it automatically finds the correct NSView object to receive that event.

Note

You can look at the interface (#include) file NSView.h in the /System/Library/Frameworks/AppKit.framework/Headers folder if you are interested in seeing the names and arguments of the methods that the NSView class implements. In fact, all of Cocoa’s Application Kit framework classes have interface (.h) files in the same folder. Because you will frequently refer to its contents, you may want to create a shortcut to this folder from your computer’s root folder. For convenience, we’ll use such a shortcut — from now on we’ll use the notation /AppKit/filename.h to stand for the file /System/Library/Frameworks/AppKit.framework/Headers/filename.h.

You can also view the documentation for the Foundation and AppKit frameworks on your hard disk using PB and on Apple’s web site.

The Objective-C program Tiny.m consists of a function called main( ), which is called by the operating system to start the program. The main( ) function in Tiny.m isn’t very complicated. Here it is:

int main(  )
{
    // create the autorelease pool
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    // create the application object
    NSApp = [NSApplication sharedApplication];

    // set up the window and drawing mechanism
    setup(  );

    // run the main event loop
    [NSApp run];

    // we get here when the window is closed

    [pool release];                 // release the pool
    return(EXIT_SUCCESS);
}

The first statement in the main( ) function creates an NSAutoreleasePool, which is used by Cocoa’s garbage-collection system.

After the autorelease pool is created, the program allocates an NSApplication object by sending the alloc message to the NSApplication class (every Cocoa program must have exactly one NSApplication object). This object is created with the sharedInstance method, which automatically allocates an NSApplication object, initializes it, and adds the object to the autorelease pool. The id of this object is then assigned to the global id variable NSApp . Global variables are a rarity in Cocoa for style and software-engineering reasons, but it makes sense to be able to send messages to the NSApplication object from any part of the program because of its crucial role.

Tip

The name “NSApp” violates the convention that class names start with capital letters while variables that point to objects start with lowercase letters; alas, NSApp is a very special object!

The second statement in main( ) calls the function setup( ), which contains the code that makes the Tiny program unique. We’ll discuss this function in detail in the next section.

The third statement, [NSApp run], is a message to the NSApplication object to run the program’s main event loop. The event loop is a system that usually sits idle, waiting to respond to the user’s pressing a key on the keyboard or moving or clicking the mouse. It can also respond to timed and internal events. The event loop is part of the NSApplication class — you never see it or have to do much with it. Unlike event loops in some other window systems, Cocoa’s are mostly automatic. The event loop terminates when the NSApp object is sent an NSApp or stop: message; this usually happens when the user chooses the Quit menu command. The NSApp message causes NSApp to call exit( ), terminating the program. The stop: message causes [NSApp run] to exit. This distinction can be useful for advanced Cocoa programming, as we’ll see later in this book.

The next line in Tiny.m frees the autorelease pool. Although you don’t strictly need to do this — the underlying operating system will automatically free those resources when the application exits — it’s good programming style to free memory that you no longer need.

Windows, Views, Delegates, and the setup( ) Function

Now it’s time to look at the workhorse of Tiny.m , the setup( ) function. We’ll try to digest it in pieces. Here is the first part of the function:

NSWindow *myWindow = nil;
NSView   *myView  = nil;
NSRect    graphicsRect;

// now create the window

graphicsRect = NSMakeRect(100.0, 350.0, 400.0, 400.0);

The first two lines set up local variables that will be used to hold the ids of the NSWindow and NSView objects that will be created. They are initialized to nil, which is a pointer to the empty object. (That is, it is a pointer to 0; messages sent to nil are ignored.) The third line creates a local variable that will hold the location on the screen where Tiny.m will draw its window.

The Cocoa Foundation provides three C typedefs for doing graphics (NSPoint, NSSize, and NSRect), which are defined in the following code. If you’re interested, you can find their declarations in the file NSGeometry.h in the /System/Library/Frameworks/Foundation.framework/Headers directory (we’ll refer to this file as /Foundation/NSGeometry.h).

typedef struct _NSPoint {
    float x;
    float y;
} NSPoint;

typedef struct _NSSize {
    float width;       /* should never be negative */
    float height;      /* should never be negative */
} NSSize;

typedef struct _NSRect {
    NSPoint origin;
    NSSize size;
} NSRect;

The function NSMakeRect( ) is simply a convenient shorthand for creating a rectangle that has a particular origin and size. Instead of using this:

graphicsRect = NSMakeRect(100.0, 350.0, 400.0, 400.0);

we could have used:

graphicsRect.origin.x =    100.0;
graphicsRect.origin.y =    350.0;
graphicsRect.size.width =  400.0;
graphicsRect.size.height = 400.0;

The graphicsRect contains the details of where the new window will be located and how big it will be. The window itself gets created in the next Tiny.m program line, when the alloc message is sent to the NSWindow class (recall that alloc is a class method). The new instance object is then initialized within the nested initWithContentRect:styleMask:backing:defer: message. The id of the new NSWindow object that is created is assigned to the variable myWindow:

myWindow = [ [NSWindow alloc]
           initWithContentRect: graphicsRect
                     styleMask: NSTitledWindowMask 
                                |NSClosableWindowMask 
                                |NSMiniaturizableWindowMask
                       backing: NSBackingStoreBuffered
                         defer: NO ];

One of the many nice features of Cocoa’s Objective-C interface is that arguments are labeled, which makes Objective-C programs easy to read. In the example above, the four arguments are initWithContentRect:, styleMask:, backing:, and defer:. After each colon are the arguments themselves.

Let’s look at each of the arguments:

initWithContentRect: graphicsRect

Specifies where the window will be created and how large it will be. In this case, the location of the lower-left corner is at (100.0,350.0) and the size is 400 pixels square. (The screen origin — the point (0.0,0.0) — is the pixel at the lower-left corner of the Mac OS X screen.)

style: NSTitledWindowMask|NSClosableWindowMask| NSMiniaturizableWindowMask

Tells the Window Server to display the window with a title bar, a close button, and a miniaturize button. (The vertical bar is the Objective-C bitwise OR operator, which causes the bits within the numerical constants to be OR-ed together.) Most Mac OS X windows have title bars that contain titles. To set up a window without a title bar, omit the NSTitledWindowMask argument. These and other window attributes are defined in the file /Appkit/NSWindow.h.

backing: NSBackingStoreBuffered

Specifies which kind of backing to use. Windows can have three kinds of backing: retained, buffered, or none. Retained backing means that visible portions of the window that a program draws are written directly to screen memory, but that an off-screen buffer is set up to retain nonvisible portions that are obscured by other windows. Thus, if the window is covered by another window and then exposed, the Window Server can redraw it without any work on the part of your program. Buffered windows use the off-screen buffer as an input buffer, and the buffer’s contents are transferred to the screen when the window is flushed. Windows with no backing have no off-screen memory; if they are covered and then exposed, they must be redrawn, and might momentarily flash white while that redrawing takes place. Buffered windows are most common in Cocoa.

defer: NO

Tells the Window Server that we want our window created right away, rather than later.

Remember, all of these arguments make up a single Objective-C method, whose proper name is initWithContentRect:styleMask:backing:defer:.

Unlike in C++, you cannot leave off an argument and get a default value!

After the long myWindow statement executes, the myWindow variable contains the id of the window created with the attributes provided. We can then send messages to the window by sending messages to that id, as we do in the next statement. The following message sets the window’s title to the string “Tiny Application Window”. The at-sign directive, @"", tells the compiler to create an NSString object with the text “Tiny Application Window”, rather than creating a char * string:

[myWindow setTitle: @"Tiny Application Window"];

The next four statements in Tiny.m create an object of the NSView class and set up the window for drawing. We need to describe the NSView class before we can discuss these statements thoroughly.

Views

The NSView class and its subclasses are the primary mechanism by which Cocoa users and applications interact. To draw on the screen, an application invokes NSView instance methods to establish communication with the Window Server and then sends the NSView instance Quartz drawing commands. Going the other way, the AppKit will send a message to an object of the NSView class when the user does something which creates an event, like clicking the mouse or pressing a key on the keyboard.

NSView objects represent rectangular chunks of screen real estate inside a window. Many of the interesting Cocoa objects — sliders, buttons, matrices, and so on — are instances of NSView subclasses. Programmers use the NSView class by subclassing it. NSView is an abstract superclass ; it contains the functionality that many other classes need and therefore inherit, but instances of the NSView class itself are rarely used.

One of the most important methods in the NSView class is drawRect:, which is invoked when its containing view (or window) wants your view to draw itself. (Cocoa invokes the drawRect: method automatically for you.)

For this example, we created a subclass of the NSView class called DemoView. This subclass adds no instance variables to what it inherits but it does override NSView’s drawRect: method with a new one that draws the fancy design shown in Figure 4-1. Here is the interface for the DemoView class:

@interface DemoView : NSView
{
}
- (void)drawRect:(NSRect)rect;
@end

This class is referenced by the last four lines of the setup( ) function, as follows:

// create the DemoView for the window
myView =[[[DemoView alloc] initWithFrame:graphicsRect]
                           autorelease];
[myWindow setContentView:myView ];
[myWindow setDelegate:myView ];
[myWindow makeKeyAndOrderFront: nil];

The first of these four statements contains nested messages that create and initialize the DemoView object called myView. The second statement sets up the myView that we’ve just created as the content view of the NSWindow object that we created earlier. Every window contains precisely one content view, which represents the area of the window that is accessible to the application. That is, the content view contains the entire window except the title bar, border, and scroller (if present). The setContentView: method also changes the offset and the size of the myView object that we created, so that it is precisely aligned with the window.

The third statement, [myWindow setDelegate:myView], delegates to the myView object the responsibility of responding to certain messages sent to the myWindow object. One such message is windowWillClose:; we’ll see how it works shortly.

The final statement sends the makeKeyAndOrderFront: message to myWindow. This message forces myWindow to be displayed in front of (on top of ) all the other on-screen windows and makes it the key window, or the window that accepts keyboard events. The argument nil doesn’t do anything here; it’s just a placeholder. The reason that the makeKeyAndOrderFront: method contains the argument is so that it can be used with IB.

Tip

As we noted earlier, the makeKeyAndOrderFront: message in this example does not result in the window’s being brought to the front of the view screen. We think that this is because the message is sent before the application’s main event loop is running. One day we hope to have a solution to this problem. If you find the answer, please send it to us and we’ll post it on the O’Reilly web site.

Drawing with Quartz Inside a View Object

The actual drawing of the fancy pattern shown in Figure 4-1 happens in the DemoView drawRect: method. The drawing code in this example is not optimized in any way, but for now it will do.

The [myView drawRect:] message is invoked (called) automatically when the DemoView is first displayed on the screen. This method executes the following code:

#define X(t) (sin(t)+1) * width * 0.5
#define Y(t) (cos(t)+1) * height * 0.5

- (void)drawRect:(NSRect)rect
{
    double f,g;
    double const pi = 2 * acos(0.0);
    int n = 31;

    float width  = [self bounds].size.width;
    float height = [self bounds].size.height;

    // clear the background

    [[NSColor whiteColor] set];
    NSRectFill([self bounds]);

    // these lines trace two polygons with n sides
    // and connect all of the vertices with lines

    [[NSColor blackColor] set];

    for (f=0; f<2*pi; f+=2*pi/n) {
      for (g=0; g<2*pi; g+=2*pi/n) {
        NSPoint p1 = NSMakePoint(X(f),Y(f));
        NSPoint p2 = NSMakePoint(X(g),Y(g));

        [NSBezierPath strokeLineFromPoint:p1 toPoint:p2];
       }
    }
}

The variables width and height are set up to be the width and height of the myView object. We get these values by invoking the bounds method on the current object ([self bounds] ). This returns the exact size of the area in which the myView object is allowed to draw.

Tip

Because the coordinate systems of NSViews can be scaled and translated, Cocoa provides two methods for determining the current size of each NSView. The message [self bounds] returns the size of the NSView in its own coordinate system, whereas the message [self frame] returns the size of the NSView in the coordinate system of its containing view. If this sounds confusing, don’t worry: we’ll explain coordinate systems in considerably more detail in Chapter 14 and Chapter 15.

The next statement sets the current drawing color to whiteColor. Then a built-in Mac OS X function, NSRectFill( ) , is called that fills the rectangle returned in [self bounds] with a white background. This has the effect of making the entire myView area white. We then change the current drawing color to blackColor before drawing the lines of the pattern.

The #define statements create two macros that will be used for translating from polar to rectangular coordinates. Once these two functions are defined, we create an inner loop and an outer loop that connect all of the lines. To draw the lines we use the NSBezierPath class, which has a collection of class methods for drawing lines, circles, and Bezier paths.

This completes our discussion of the Tiny application. Don’t worry if you don’t understand all these statements (especially those starting with the macros) at this point.

Get Building Cocoa Applications: A Step by Step Guide now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.