3.2. Windows and Views

The most basic user interface component is a UIWindow object. A window provides the backing for displaying information within your application. It acts as a picture frame to which you can anchor content. While windows that you create on the desktop appear with title bars, frames, and buttons, the iPhone's UIWindow object has no visual features—it is essentially a view port. You will generally create only a single UIWindow object to use throughout the life of your application, and anchor one or more objects derived from UIView to it.

The UIView class is a base class designed to accommodate the drawing of objects in windows. If the UIWindow were a picture frame, think of the UIView class as the canvas. Many classes are derived from UIView to create visual objects that render text boxes, tables, and web pages. The base class represents a generic level of functionality that provides basic gestures, drawing routines, and responders. You'll be able to create your own custom classes based on UIView to render your own types of objects, or anchor other UI components to a UIView object to display them.

If you're using Interface Builder in your project, the window and view may be automatically generated for you when the application starts. The instructions for doing this are located in the Interface Builder NIB file, which acts as a sort of resource fork for your user interface. If you're not using Interface Builder, you'll create these objects directly within your code.

3.2.1. Creating a Window and View

Before you can display anything on the iPhone screen, you must create a window to hold content. To build a window, you need a frame. A frame is a rectangular region on the screen where your content should be displayed. The underlying structure containing this information is a CGRect structure. A CGRect structure contains two elements: the coordinates for the upper-left corner of the window (the origin) and the window's width and height (the size). Every object that can display itself on the screen has a frame defining its display region, although many higher-level classes automatically calculate this for you. Others are set automatically when a view object is initialized, by means of an initialization method named initWithFrame.

When creating the main window, you'll create a frame whose coordinates are offset to the screen itself. Subsequent objects, however, are offset to the object they are anchored to. For example, the frame of a view anchored to the window will be offset to the coordinates of the window, and not the screen. The objects anchored to the view will be offset to the coordinates of the view, and so on.

An application uses the entire iPhone screen when it is displayed, so you should assign the window a set of coordinates reflecting the region of the entire screen. Two methods exist inside a class named UIScreen to determine this.

The bounds method returns the entire screen's boundaries, including the space used by the status bar:

CGRect screenBounds = [ [ UIScreen mainScreen ] bounds ];

The applicationFrame method returns the displayable portion of the screen in which your application will be displayed. This space does not include the status bar:

CGRect screenBounds = [ [ UIScreen mainScreen ] applicationFrame ];

This region, assigned to the CGRect structure screenBounds, is then used to create and initialize a new UIWindow object:

self.window = [ [ UIWindow alloc ] initWithFrame: screenBounds ];

The window frame is now created, but contains nothing and has not been instructed to display anything—it is simply an invisible object sitting on the screen. You now require an object that can render content inside the frame, and therefore you'll need an object based on the UIView class to fill the window.

As you've just learned, the window's offset is relative to the screen. A view's offset, however, is relative to the window it's attached to. While the window's offset on the screen begins at the pixel underneath the status bar (0x20), the view's offset within the window begins at 0x0, specifying the upper-left corner of the window. To accomplish this, you'll obtain the applicationFrame bounds and adjust the vertical offset so that it is zeroed with the window:

CGRect viewBounds = [ [ UIScreen mainScreen ] applicationFrame ];
viewBounds.origin.y = 0.0;

You'll now use viewBounds to specify the offset and dimensions of the view class:

UIView *myView = [ [ UIView alloc ] initWithFrame: viewBounds ];

NOTE

 

This will change slightly as you learn more about view controllers. When using view controllers, the window will use the bounds of the entire screen, including the status bar, and the view will use the application's frame as is. This allows the view controller to correctly handle screen rotations.

3.2.2. Displaying the View

You've created the window and view pair, but neither has been displayed on the screen. To do this, anchor the view to the window as a subview:

[ window addSubview: myView ];

Now bring the window to the front and display it using the UIWindow class's makeKeyAndVisible method:

[ window makeKeyAndVisible ];

3.2.3. HelloView: Most Useless Application Ever

Before you even get to "Hello, World!" you'll experiment with an even more useless application, "Hello, View!" This application does nothing more than create a window and view pair. In fact, because the UIView class is just a base class, it can't even display any text for you. All you'll see is a black screen. What this application does do is serve as the first lines of code any GUI application on the iPhone will use.

You can compile this application, shown in Examples Example 3-1, Example 3-2, and Example 3-3, with the SDK by creating a window-based application project named HelloView. As with most examples in this chapter, you may wish to remove Interface Builder from the project to get a feel for how each object is individually created in code.

Example 3-1. HelloView application delegate prototypes (HelloViewAppDelegate.h)
#import <UIKit/UIKit.h>

@interface HelloViewAppDelegate : NSObject <UIApplicationDelegate> {
    /* The one and only window in your application */
    UIWindow *window;

    /* The view class you'll be displaying in the window */
    UIView *myView;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;

@end

Example 3-2. HelloView application delegate (HelloViewAppDelegate.m)
#import "HelloViewAppDelegate.h"

@implementation HelloViewAppDelegate

@synthesize window;

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    CGRect screenBounds = [ [ UIScreen mainScreen ] applicationFrame ];
    CGRect windowBounds = screenBounds;
    windowBounds.origin.y = 0.0;

    /* Initialize the window */
    self.window = [ [ UIWindow alloc ] initWithFrame: screenBounds ];

    /* Initialize the view */
    myView = [ [ UIView alloc] initWithFrame: windowBounds ];

    /* Anchor the view to the window */
    [ window addSubview: myView ];

    /* Make the window key and visible */
    [ window makeKeyAndVisible ];
}

- (void)dealloc {
    [ myView release ];
    [ window release ];
    [ super dealloc ];
}

@end

                                          

Example 3-3. HelloView main (main.m)
#import <UIKit/UIKit.h>

int main(int argc, char *argv[]) {

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

      /* Call with the name of our application delegate class */
    int retVal = UIApplicationMain(argc, argv, nil, @"HelloViewAppDelegate");
    [pool release];
    return retVal;
}

                                          

3.2.4. What's Going On

The HelloView application doesn't visibly seem to do much, but its internal clockwork functions as follows:

  1. When the application starts, its main function is invoked, just as in a regular C program. This hooks into Objective-C land and instantiates an application. The application then notifies its delegate, HelloViewAppDelegate, which is specified in the call to UIApplicationMain. The main function is also responsible for initializing an auto-release pool. Auto-release pools are used extensively throughout Apple's Cocoa framework to dispose of objects that have been designated as autorelease when they were created. This tells the application to simply throw them away when it's done with them, and they are deallocated later on.

  2. The underlying framework calls the HelloViewAppDelegate class's applicationDidFinishLaunching method once the object has initialized. This is where the Objective-C portion of the application begins life.

  3. The system calls the UIScreen class's applicationFrame method to return the coordinates and size of the application's display window. This will create a new window where the application's main view will reside. Keep in mind that you'll later change this to use the entire screen region as your applications become more advanced in nature.

  4. The application creates the main view class, using a display region beginning at 0x0 (the upper-left corner of the window). It then sets the view as the window's content.

  5. The application delegate finally instructs the window to come to the front and display itself. This displays the view, which presently has no content.

3.2.5. Deriving from UIView

The HelloView example shows the very minimal code needed to construct and display a window/view pair. Because the UIView class itself is merely a base class, it didn't actually display anything. To create a useful application, you'll need to either anchor a more useful object to the UIView or derive a new UIView class to add more functionality. The iPhone SDK provides many subclasses of the UIView class that offer differing types of functionality. You can also create your own subclass to build a custom view.

To derive a subclass from UIView, write a new interface and implementation declaring the subclass. The following snippet creates a class named MainView as a subclass of the UIView class:

@interface MainView : UIView
{

}
- (id)initWithFrame:(CGRect)rect;
- (void)dealloc;

@end

The MainView class inherits from the UIView class, so at the moment it does the equivalent of the UIView class—nothing. In order to make this class useful, you'll add new functionality. Below is a new version of the above snippet, adding variable storage for a text box class named UITextView. In reality, you could easily attach a UITextView object to the window itself, because it is derived from the UIView class. For now, you'll incorporate it into your custom class:

#import <UIKit/UITextView.h>

@interface MainView : UIView
{
    UITextView *textView;
}
- (id)initWithFrame:(CGRect) rect;
- (void)dealloc;

@end

You'll notice two methods above, initWithFrame and dealloc. These methods are the default initializer and destructor methods, and will be overridden in your subclass, MainView, to expand the functionality of the UIView class.

NOTE

Classes use a common constructor method named alloc, but you shouldn't override these methods. Your own custom initialization code belongs in initializer methods such as init and initWithFrame.

As you've seen, the initWithFrame method is called when the view is first instantiated and is used to initialize the class. A frame is passed to it in order to define its display region. You should place any code that initializes variables or other objects inside this method. The second method, dealloc, is called when the object is disposed of. You must release any resources previously allocated within your class here, so that they will be deallocated when the object is destroyed. Both methods invoke their superclass methods to allow the UIView class to handle its own internal functions.

Here are the templates for these two important methods:

@implementation MainView
- (id)initWithFrame:(CGRect)rect {

    /* Call the super class's initWithFrame method first,
     * to initialize the UIView object */
    self = [ super initWithFrame: rect ];

    /* If the object has already been initialized, self will be nil */

    if (self != nil) {

        /* Initialize your class's variables here */

        /* Allocate your class's initial resources here */
    }

    return self;
}

- (void)dealloc
{
    /* Deallocate your class's resources here */

    /* Call the super class's dealloc method,
     * to deallocate any resources held by UIView */

    [ super dealloc ];
}
@end

3.2.6. HelloWorld: The Traditionally Useless Application

Now that you've learned how to derive a UIView class, you've got everything you need to write an application that does something—albeit a mostly useless something. In the tradition of our respected legends Kernighan and Ritchie, we now present the official useless "Hello, World!" application for the iPhone SDK.

You can compile this application, shown in Examples Example 3-4, Example 3-5, and Example 3-6, with the SDK by creating a window-based application project named HelloWorld. You'll want to remove Interface Builder from the project to see how all objects are created.

Example 3-4. HelloWorld application delegate prototypes (HelloWorldAppDelegate.h)
#import <UIKit/UIKit.h>

@interface MainView : UIView
{
    UITextView *textView;
}

@end

@interface HelloWorldAppDelegate : NSObject <UIApplicationDelegate,
 UITextViewDelegate> {
    UIWindow *window;
    MainView *myMainView;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;

@end

Example 3-5. HelloWorld application delegate (HelloWorldAppDelegate.m)
#import <UIKit/UITextView.h>
#import <UIKit/UIColor.h>
#import <UIKit/UIFont.h>
#import "HelloWorldAppDelegate.h"

@implementation MainView

- (id)initWithFrame:(CGRect) rect {
    self = [ super initWithFrame: rect ];

    if (self != nil) {
        textView = [ [ UITextView alloc] initWithFrame: rect ];
        textView.text = @"Hello, World!";

        [ self addSubview: textView ];
    }

    return self;
}

- (void)dealloc {
    [ textView release ];
    [ super dealloc ];
}
@end

@implementation HelloWorldAppDelegate

@synthesize window;

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    CGRect screenBounds = [ [ UIScreen mainScreen ] applicationFrame ];
    CGRect windowBounds = screenBounds;
    windowBounds.origin.y = 0.0;

    self.window = [ [ [ UIWindow alloc ] initWithFrame: screenBounds ]
        autorelease
    ];

    myMainView = [ [ MainView alloc ] initWithFrame: windowBounds ];

    [ window addSubview: myMainView ];
    [ window makeKeyAndVisible ];
}

- (void)dealloc {
    [myMainView release];
    [window release];
    [super dealloc];
}

@end

                                          

Example 3-6. HelloWorld main (main.m)

 

#import <UIKit/UIKit.h>

int main(int argc, char *argv[]) {

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, @"HelloWorldAppDelegate");
    [pool release];
    return retVal;
}

                                          

3.2.7. What's Going On

The HelloWorld example contains everything you've seen so far, with the addition of a subclass of UIView, MainView, which can display text:

  1. The application instantiates in the same way as before; by calling the program's main function, which invokes the UIApplicationMain function and subsequent notifications to the HelloWorldAppDelegate delegate class.

  2. Instead of creating a generic UIView class, the application instantiates its own class named MainView, which is derived from the UIView class.

  3. The MainView class's initWithFrame method is called, which in turn calls its superclass (UIView) and its own initWithFrame method to let UIView do the work of creating itself.

  4. A UITextView class, which you'll learn more about in the next section, is instantiated and attached to the MainView object. This text view is instructed to display the text, "Hello, World!" The UITextView object is added as a subview to the MainView object, and the MainView object is in turn added as a subview to the UIWindow object.

  5. The window is instructed to become visible, displaying the MainView object, which displays the UITextView object attached to it.