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.
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 typedef
s 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 theNSTitledWindowMask
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.
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.
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.