Chapter 3. Introduction to UIKit

UIKit is the largest iPhone framework in terms of file size, and rightly so—it’s responsible for all user interface functions from creating windows and text boxes to reading multitouch gestures and hardware sensors. All of the graphical pleasantries that make the iPhone seem easy-to-use rely on the UIKit framework to deliver a polished and unified interface. The same UIKit APIs are available to all iPhone applications, and understanding how to use this framework will allow you to take advantage of the same tools that make Apple’s own stock apps spectacular.

UIKit is more than a mere user interface kit; it is also the runtime foundation for iPhone GUI applications. When an application is launched, its main( ) function instantiates a UIApplication object within UIKit. This class is the base class for all applications having a user interface on the iPhone, and it provides the application access to the iPhone’s higher-level functions. In addition to this, common application-level services such as suspend, resume, and termination are functions of the UIApplication object.

To tap into the UIKit, your application must be linked to the UIKit framework. As a framework, UIKit is a type of shared library. So, using the compiler tool chain, UIKit can be linked to your application by adding the following arguments to the compiler arguments we described in Chapter 2:

$ arm-apple-darwin-gcc -o MyApp MyApp.m -lobjc 
    -framework CoreFoundation 
    -framework Foundation 
    -framework UIKit

To add this option to the sample makefile from the previous chapter, add the UIKit framework to the linker flags section so that the library is linked in:

LDFLAGS =    -lobjc 
        -framework CoreFoundation 
        -framework Foundation 
        -framework UIKit

Basic User Interface Elements

This chapter is designed to get you up and running with a basic user interface. UIKit supports the following basic user interface elements. The more advanced features of UIKit are covered in Chapter 7.

Windows and views

Windows and views are the base classes for creating any type of user interface. A window represents a geometric space on a screen, while a view acts like a container for other objects. Smaller user interface components, such as navigation bars, buttons, and text boxes are all attached to a view, and that view is anchored to a window. Think of a window as the frame of a painting and the view as the actual canvas. A window can only frame up one view, but views can contain smaller subviews, including other views.

A controlling view is a view that controls how other views are displayed on the screen. The controlling view performs transitions to other views and responds to events occurring on the screen.

Text views

Text views are specialized view classes for presenting editor windows to view or edit text or HTML. The Notepad application is a good example of a simple text view. They are considered humble and are rarely used in light of UIKit’s repertoire of more spectacular user interface tools.

Navigation bars

The iPhone user interface treats different screens as if they are “pages” in a book. Navigation bars are frequently used to provide a visual prompt to allow the user to return “back” to a previous view, supply them with buttons to modify elements on the current screen page, and provide formatted titles to the screen page they are viewing. Navigation bars are found in nearly all preloaded iPhone applications.

Transitions

A single screen page is rarely enough for any application to function, especially on a mobile device. Consistent with the spirit of Apple’s user-friendly interfaces, window transitions were introduced with the iPhone to allow the user to perceive navigation through their application like pages in a book. Window transitions are used to make this visual transition from one window to another, and provide various types of different transitions from the familiar page flipping effect to fades and twists.

Alert sheets

The equivalent to a pop-up alert window on the iPhone is an alert sheet. Alert sheets appear as modal windows that slide up from the bottom when an operation requires the user’s attention. They are frequently seen on preloaded iPhone applications when a user attempts to delete a number of items or clear important data, such as voicemail. Alert sheets can be programmed to ask the user any question, and present them with any number of different options. They prove useful in parts of an application needing immediate attention.

Tables

Tables are really lists that can be used to display files, messages, or other types of collections. They are used for selection of one or more items in a list-like fashion. The table objects are very flexible and allow the developer to define how a table cell should look and behave.

Status bar manipulation

The status bar is the small bar appearing at the top of the iPhone screen, and displays the time, battery life, and signal strength. The status bar’s color, opacity, and orientation can be customized, and the status bar can be made to display icon images to notify the user of a particular application state.

Application badges

Applications needing to notify the user of time-sensitive information have the ability to display badges on the main applications screen. This alerts the user that the application needs attention, or that the user has messages or other information waiting to be viewed. These are used heavily by applications using the EDGE network to deliver messages.

Status bar icons

The status bar is the small bar appearing at the top of the iPhone screen, and displays the time, battery life, and signal strength. Changes can be made to the status bar depending on the application’s needs for style, opacity, and orientation. Images can also be added to the status bar to notify the user of an ongoing operation (such as an alarm or background process).

Windows and Views

The most basic user interface component is a window. A window is a region of screen space: a picture frame. UIWindow is the iPhone’s base window class and is derived from lower level functions that respond to mouse events, gestures, and other types of events that would be relevant to a window. The UIWindow class is ultimately responsible for holding the contents of a UIView object (the picture that fits in the window’s frame). UIView is a base class from which many other types of display classes are derived. The window itself can only hold one object, whereas the UIView object is designed to accommodate many different types of subobjects, including other views. The two classes go hand-in-hand with each other, and both are required to display anything on the iPhone screen.

Creating a Window and View

Before you can display anything on the iPhone screen, you must create a window to hold content, and to build a window, you need a display region. A display region is a fancy term for rectangle and represents the portion of the screen where the window should be displayed. The underlying structure itself is a rectangle structure named CGRect that contains two pieces: the coordinates for the upper-left corner of the window and the window’s size (its width and height). Every object displayed on the screen has a display region defining its display area. Most are set when the object is initialized, via an initWithFrame method. Others must be set after the fact using a ubiquitous method named setFrame. In the case of the main window, the region’s coordinates are offset to the screen itself; however, all subsequent objects (including the window’s view) are offset to the object that contains it. As other objects are nested inside the view, those objects’ regions will be offset to the view, and so on.

An application uses the entire iPhone screen when it is displayed, and so the window should be assigned a set of coordinates reflecting the view region of the entire screen. This region can be obtained from a static method found inside a class named UIHardware.

CGRect windowRect = [ UIHardware fullScreenApplicationContentRect ];

This region, named windowRect above, is then used to create and initialize a new UIWindow object:

UIWindow *window = [ UIWindow alloc ];
[ window initWithContentRect: windowRect ];

The window frame has now been created, but contains nothing. An object that can render content is needed to place inside of it. A UIView object must be created to fill the window. Because a window can only hold one view object, the display region for it should likewise use the entire screen so that it will fill up the entire window.

CGRect viewRect = [ UIHardware fullScreenApplicationContentRect ];
viewRect.origin.x = viewRect.origin.y = 0.0;

UIView *mainView = [ [ MainView alloc ] initWithFrame: viewRect ];

Displaying the View

The window and view pair has been created, but the view has not been displayed on the screen. To do this, assign the window and instruct the window to display:

    [ window setContentView: mainView ];
    [ window orderFront: self ];
    [ window makeKey: self ];
    [ window _setHidden: NO ];

The Most Useless Application Ever

Before we even get to “Hello, World!”, we need an even more useless application, “Hello, Window!”. This application does nothing more than to create a window and view pair. In fact, because the base UIView class is just a container 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 few lines of code any GUI application on the iPhone will use.

This application, shown in Example 3-1 and Example 3-2, can be compiled from the tool chain using the following command line:

$ arm-apple-darwin-gcc -o MyExample MyExample.m -lobjc 
    -framework CoreFoundation -framework Foundation -framework UIKit
Example 3-1. Example window and view (MyExample.h)
#import <CoreFoundation/CoreFoundation.h>
#import <UIKit/UIKit.h>

@interface MyApp : UIApplication
{
    UIWindow *window;
    UIView *mainView;
}
- (void)applicationDidFinishLaunching:
    (NSNotification *)aNotification;
@end
Example 3-2. Example window and view (MyExample.m)
#import "MyExample.h"

int main(int argc, char **argv)
{
    NSAutoreleasePool *autoreleasePool = [
        [ NSAutoreleasePool alloc ] init
    ];
    int returnCode = UIApplicationMain(argc, argv, [ MyApp class ]);
    [ autoreleasePool release ];
    return returnCode;
}

@implementation MyApp

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    window = [ [ UIWindow alloc ] initWithContentRect:
        [ UIHardware fullScreenApplicationContentRect ]
    ];

    CGRect windowRect =
        [ UIHardware fullScreenApplicationContentRect ];
    windowRect.origin.x = windowRect.origin.y = 0.0f;

    mainView = [ [ UIView alloc ] initWithFrame: windowRect ];

    [ window setContentView: mainView ];
    [ window orderFront: self ];
    [ window makeKey: self ];
    [ window _setHidden: NO ];
}
@end

What’s Going On

The “Hello, Window!” application flows like this:

  1. When the application starts, its main( ) function is called, just as in a regular C program. This hooks into Objective-C land and instantiates an application class named MyApp, which is derived from UIKit’s UIApplication class. 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 are created. This tells the application to simply throw them away when it’s done with them, and they are deallocated later on.

  2. The UIApplication class’s applicationDidFinishLaunching method is called by the underlying application object’s framework once the object has initialized. This is where the Objective-C application begins its life.

  3. A call to UIHardware’s fullScreenApplicationContentRect method is called to return the coordinates and size of the physical screen. This is then used to create a new window where the application’s main view resides.

  4. The main view is then created, using a display region beginning at 0×0 (the upper-left corner of the window). The view is set as the window’s content.

  5. The window is then instructed to bring itself to the front and show itself. This displays the view, which presently has no content.

Deriving from UIView

The “Hello, Window!” example showed 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, a new class can be derived from UIView, allowing its methods to be overridden to add functionality. This controlling view can then display other objects, such as text boxes, images, etc.

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

@interface MainView : UIView
{

}

- (id)initWithFrame:(CGRect)rect;
- (void)dealloc;
@end

At the very least, two UIView class methods should be overridden. The initWithFrame method is called when the view is first instantiated and is used to initialize the view class. A display region is passed in to define its coordinates (offset to its parent) and size it should display as. Any code that initializes variables or other objects can go into this method. The second method, dealloc, is called when the UIView object is disposed of. Any resources previously allocated within your class should be released here.

These two methods are the basis for all other activity within the view class. Here are the templates for them:

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

    if ((self == [ super initWithFrame: rect ]) != nil) {

        /* Initialize member variables here */

        /* Allocate initial resources here */
    }

    return self;
}

- (void)dealloc
{
    /* Deallocate any resources here */

    [ self dealloc ];
    [ super dealloc ];
}
@end

The Second Most Useless Application Ever

Now that you know 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 ancestors, we now present the official useless “Hello, World!” application.

This application, shown in Example 3-3 and Example 3-4, can be built using the same command-line arguments as the previous example:

$ arm-apple-darwin-gcc -o MyExample MyExample.m -lobjc 
    -framework CoreFoundation -framework Foundation -framework UIKit
Example 3-3. “Hello World!” example (MyExample.h)
#import <CoreFoundation/CoreFoundation.h>
#import <UIKit/UIKit.h>
#import <UIKit/UITextView.h>

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

@interface MyApp : UIApplication
{
    UIWindow *window;
    MainView *mainView;
}
- (void)applicationDidFinishLaunching:
    (NSNotification *)aNotification;
@end
Example 3-4. “Hello World!” example (MyExample.m)
#import "MyExample.h"

int main(int argc, char **argv)
{
   NSAutoreleasePool *autoreleasePool = [
        [ NSAutoreleasePool alloc ] init
    ];
    int returnCode = UIApplicationMain(argc, argv, [ MyApp class ]);
    [ autoreleasePool release ];
    return returnCode;
}

@implementation MyApp

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    window = [ [ UIWindow alloc ] initWithContentRect:
        [ UIHardware fullScreenApplicationContentRect ]
    ];

    CGRect rect = [ UIHardware fullScreenApplicationContentRect ];
    rect.origin.x = rect.origin.y = 0.0f;

    mainView = [ [ MainView alloc ] initWithFrame: rect ];

    [ window setContentView: mainView ];
    [ window orderFront: self ];
    [ window makeKey: self ];
    [ window _setHidden: NO ];
}
@end

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

    if ((self == [ super initWithFrame: rect ]) != nil) {

        textView = [ [ UITextView alloc ] initWithFrame: rect ];
        [ textView setTextSize: 18 ];
        [ textView setText: @"Hello, World!" ];
        [ self addSubview: textView ];
    }

    return self;
}

- (void)dealloc
{
    [ self dealloc ];
    [ super dealloc ];
}

@end

What’s Going On

The “Hello, World!” example contains everything you’ve seen so far, with the addition of a new view designed to display text:

  1. The application instantiates in the same way as before, by calling the program’s main( ) function, which creates an instance of MyApp.

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

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

  4. A UITextView, which you’ll learn more about in the upcoming "Text Views" section, is created and attached to the MainView object. This text view is given the text, "Hello, World!".

  5. The window is instructed to display, displaying the MainView object and the UITextView object attached to it.

Further Study

Now that you have something to look at in your application, play around with it for a little while before proceeding.

  • Try changing the origins and size of the frame passed to mainView. What happens to the window and its child? How about when changing the display origin of textView?

  • Check out the UIWindow.h and UIView.h prototypes in your tool chain’s include directory. You’ll find them in /usr/local/arm-apple-darwin/include/UIKit/.

Text Views

The UITextView class is based on a UIView, however, its functionality has been extended to present and allow editing of text, provide scrolling, and handle various fonts and colors. Text views can be easily abused, and are only recommended for text-based portions of an application, such as an electronic book, notes section of a program, or informational page to present unstructured information.

A UITextView object inherits from UIScroller, which is a generic scrollable class. This means that the text view itself comes pre-equipped with all scrolling functionality, so the developer can focus on presenting content rather than programming scroll bars. The UIScroller class inherits from UIView, which, as discussed in the last section, is the base class for all view classes.

Creating a Text View

Because UITextView is ultimately derived from UIView, it is created in the same fashion as the main view objects created in the last section—using an initWithFrame method.

UITextView *textView = [ [ UITextView alloc ]
    initWithFrame: viewRect ];

Once the view is created, a number of different properties can then be set.

Editing

If the text view is being used to collect user input, it will need to be made editable:

[ textView setEditable: YES ];

If the view is simply presenting data that should not be edited, this feature should be disabled.

Margins

The size of the top margin is the only margin that can be set in the text view. The value represents the number of pixels from the top of the text view that the text should be offset.

[ textView setMarginTop: 20 ];

Text properties

The text size, font and color can be set by passing point sizes and font and color objects into the class. These settings work for text and HTML uses, although they serve only as a default for HTML.

The text size is the simplest property, and is passed as an integer measuring point size.

[ textView setTextSize: 12 ];

The font is passed in as a CSS compliant string identifying the font properties.

[ textView setTextFont:
  @"font-family: Helvetica; font-style: italic; font-weight: bold" ];

The text color is a bit trickier and requires the use of another framework on the iPhone named Core Graphics. To create a simple RGB color, a set of four floating-point values for red, green, blue, and alpha (opacity) are specified with values between 0.0 and 1.0. These represent values ranging from 0% (0.0) to 100% (1.0). The values are used to create a color reference, which is then assigned in the text view as a CGColorRef (“Core Graphics color reference”).

CGColorSpaceRef colorSpace =
    CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
float opaqueRed[4] = { 1.0, 0.0, 0.0, 1.0 };
CGColorRef red = CGColorCreate(colorSpace, opaqueRed);
[ textView setTextColor: red ];

The Core Graphics framework will be explored more in Chapter 4.

Assigning Content

Two different methods can set the content of a text view: setText and setHTML. As the name suggests, invoking the setText method causes the content to be displayed as text, while the setHTML treats the content like a web page. If you try to call setText using HTML input, it will appear as HTML source code.

An example of the text display is:

[ textView setText: @"Hello, world!" ];

while HTML can be displayed like this:

[ textView setHTML: @"<b><center>Hello, World!</center></b>" ];

Displaying the Text View

Text views are generally attached to the main view of a window. This allows other subviews, such as navigation bars and controls, to be added to the same parent view later on.

[ mainView addSubview: textView ];

Example: Displaying iPhone Disclaimers

Every iPhone shipped by Apple contains a rather lengthy HTML file containing all of Apple’s legal disclaimers. This is displayed in the legal section of Apple’s Settings application. This example will take this file and display it in a text box.

To compile this application, you’ll need to include the Core Graphics framework, which contains the routines needed to mix colors.

This application, shown in Example 3-5 and Example 3-6, can be built using the tool chain on the following command line:

$ arm-apple-darwin-gcc -o MyExample MyExample.m -lobjc 
 -framework CoreFoundation -framework Foundation -framework UIKit 
 -framework CoreGraphics
Example 3-5. UITextView example (MyExample.h)
#import <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <UIKit/UITextView.h>

@interface MainView : UIView
{
        UITextView         *textView;
}

- (id)initWithFrame:(CGRect)frame;
- (void)dealloc;

@end

@interface MyApp : UIApplication
{
    UIWindow *window;
    MainView *mainView;
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
@end
Example 3-6. UITextView example (MyExample.m)
#include <stdio.h>
#import "MyExample.h"

int main(int argc, char **argv)
{
   NSAutoreleasePool *autoreleasePool = [
        [ NSAutoreleasePool alloc ] init
    ];
    int returnCode = UIApplicationMain(argc, argv, [ MyApp class ]);
    [ autoreleasePool release ];
    return returnCode;
}

@implementation MyApp

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    window = [ [ UIWindow alloc ] initWithContentRect:
        [ UIHardware fullScreenApplicationContentRect ]
    ];

    CGRect rect = [ UIHardware fullScreenApplicationContentRect ];
    rect.origin.x = rect.origin.y = 0.0f;

    mainView = [ [ MainView alloc ] initWithFrame: rect ];

    [ window setContentView: mainView ];
    [ window orderFront: self ];
    [ window makeKey: self ];
    [ window _setHidden: NO ];
}
@end

@implementation MainView
- (id)initWithFrame:(CGRect)rect {
    if ((self == [ super initWithFrame: rect ]) != nil) {
        FILE *file;
        char buffer[262144], buf[1024];

        textView = [ [ UITextView alloc ] initWithFrame: rect ];
        [ textView setTextSize: 12 ];

        file = fopen("/Applications/Preferences.app/English.lproj/
legal-disclaimer-iphone.html", "r");

        if (!file) {
            CGColorSpaceRef colorSpace =
                CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
            float opaqueRed[4] = { 1.0, 0.0, 0.0, 1 };
            CGColorRef red = CGColorCreate(colorSpace, opaqueRed);
            [ textView setTextColor: red ];

            [ textView setText: @"ERROR: File not found" ];
        } else {
            buffer[0] = 0;
            while((fgets(buf, sizeof(buf), file))!=NULL) {
                strlcat(buffer, buf, sizeof(buffer));
            }
            fclose(file);

            [ textView setHTML:
                [ [ NSString alloc ] initWithCString: buffer ]];
        }

        [ self addSubview: textView ];
    }

    return self;
}

- (void)dealloc
{
    [ self dealloc ];
    [ super dealloc ];
}

@end

What’s Going On

The example reads in and displays the contents of a file as follows:

  1. When the application initializes, the main view is created and its initWithFrame method is called.

  2. The initWithFrame method instantiates a new UITextView object and assigns a text size.

  3. The file legal-disclaimer-iphone.html is opened using POSIX C’s fopen method.

  4. If the file cannot be found, a red color is created and an error message is set as the text of the window.

  5. If the file is found, it is read into a text buffer and then set as the HTML content of the window.

  6. The text view itself is added as a subview to the controlling view where it is displayed to the user.

Further Study

Here are some productive ways to play with this example:

  • Copy your favorite web site’s HTML over to the iPhone using SCP. Modify this example to display your file instead. What limitations does the UITextView appear to have when displaying HTML?

  • What other font styling information can you pass to the text view’s setTextFont method? See W3’s CSS specification at http://www.w3.org.

  • Check out the UITextView.h prototypes in the tool chain’s include directory. You’ll find it in /usr/local/arm-apple-darwin/include/UIKit/.

The iPhone doesn’t support toolbars in the traditional desktop sense. Because each screen of an application is considered a page in a book, Apple has made their version of the toolbar for iPhone to appear more book-like as well. In contrast to toolbars, which can display a clutter of different icons, navigation bars are limited to include a page title (e.g., “Saved Messages”), directional buttons to parent pages, and text buttons for context-sensitive functions such as turning on the speakerphone or clearing a list of items. Navigation bars can also support controls to add tabbed buttons such as the “All” and “Missed” call buttons when viewing recent calls.

Creating a Navigation Bar

To create a navigation bar, instantiate it as an object and call its initWithFrame method—just like a UIView.

UINavigationBar *navBar = [ [UINavigationBar alloc]
    initWithFrame: CGRectMake(0, 0, 320, 48)
];

The above creates a navigation bar 48 pixels high (the default) at position 0x0 (the upper-left corner of its parent view). This is the general convention, but a navigation bar can be created anywhere within the window. By using a different vertical offset, you can place the navigation bar at the bottom of the window or underneath another navigation bar.

To know when something happens on the navigation bar, such as a button press, use the navigation bar’s delegate. A delegate is an object that acts on behalf of another object. By setting the navigation bar’s delegate to self, you can have it send events such as button presses to the object that created the navigation bar.

[ navBar setDelegate: self ];

Animations are simple fade transitions that occur when transitioning from an old set of buttons to a new set—for example, if a button is changed after it is pressed. Calling enableAnimation uses these smoother transitions instead of an instantaneous change.

[ navBar enableAnimation ];

Once the navigation bar has been created and initialized, its title and buttons can then be configured. These can be changed even after the navigation bar is displayed; the bar will change to accommodate its new settings.

Setting the title

The navigation title appears as large white text centered in the middle of the bar. The title is frequently used to convey to the end user what sort of information is being displayed, for example, “Saved Messages.”

The title is created as a UINavigationItem object. This is the base class for anything that natively attaches to a navigation bar, including buttons and other objects. When the title is added to the navigation bar, its UINavigationItem object is pushed onto it like an object on a stack. Because of this, you’ll want to store a pointer to the title inside the program somewhere. This will let you go back and change the title without having to pull it off the stack first, which would reconfigure the entire navigation bar.

UINavigationItem *navItem = [ [ UINavigationItem alloc ]
    initWithTitle:@"My Example" ];
[ navBar pushNavigationItem: navItem ];

Here, navItem points to a newly created UINavigationItem object and is assigned the title “My Example.” When the user does something that would cause the title to change, navItem can be changed to something else.

[ navItem setTitle: @"Another Example" ]

Buttons and button styles

Buttons can be added to the left and/or right sides of the navigation bar. Because space is limited, the only buttons that should be added are those for functions specific to the page being displayed. At any time, the navigation bar’s buttons can be changed. With animations enabled, the user will see a smooth, animated transition to the new set of buttons.

Using the example navBar object created earlier, add two buttons to the navigation bar, one labeled “Good” and one “Evil.”

[ navBar showLeftButton:@"Good" withStyle: 0
            rightButton:@"Evil" withStyle: 0 ];

If you need only one button, you can replace the other button’s string with nil. For example, if you wanted to only provide the user with “Evil” as an option:

[ navBar showLeftButton:nil withStyle: 0
            rightButton:@"Evil" withStyle: 0 ];

In addition to setting button titles, UIKit also allows for different styles. For instance, notice that when you push the Speaker button in the iPhone’s phone application, the button turns blue. Some buttons also appear to have an arrow shape, conveying the concept of “go back” to the user. By changing the value for the withStyle parameter in the method call previously shown, you can choose from one of four different button styles. Pay special attention to the style numbers, which are zero-indexed.

Style

Description

0

Default style, just a plain gray button

1

Colors the button red, useful for warning toggles such as Delete

2

Creates the button in the shape of a left (back) arrow

3

Colors the button blue, useful for emphasis or general toggles such as Speaker

Apple may add additional styles in future releases of iPhone software, but for now, any other value defers back to the default style.

If you don’t plan on using styles at all, there’s a shortcut method to creating navigation bar buttons that gets rid of some of the work involved:

[ navBar showButtonsWithLeftTitle:@"Back"
                       rightTitle: nil
                         leftBack: YES ];

This method can be used to create a quick set of buttons with no custom styles except for the back arrow button; it is specified by passing YES for the leftBack parameter.

The navigation bar itself can be displayed in one of a few different styles. The default style is the standard gray appearance. Three different styles are presently supported.

Style

Description

0

Default style, gray gradient background with white text

1

Solid black background with white text

2

Transparent black background with white text

The style is set using the call setBarStyle:

[ navBar setBarStyle: 0 ];

Displaying the Navigation Bar

Once you’ve laid out the initial look for your navigation bar, it’s time to display it within your application. Navigation bars are attached to a view object, such as the main view you created in the last example.

[ self addSubview: navBar ];

If you would like to hide the navigation bar, just pull it off the view:

[ navBar removeFromSuperview ];

When you’re finished with the navigation bar entirely, it can be disposed of by releasing it. This can be done in the view class’s dealloc method:

[ navBar release ];

Intercepting Button Presses

At this point, your navigation bar is displayed on the screen, but the buttons do nothing. Earlier, the navigation bar’s delegate was set to self, the calling view. Because the calling view is acting on the navigation bar’s events, it will need the ability to intercept button presses.

To do this, a method named buttonClicked must be overridden. As you’ll see in the coming chapters, many different types of objects have a buttonClicked method, but take different arguments. Objective-C supports polymorphism, which allows the developer to support multiple methods sharing the same name, but with different argument types. The runtime will choose the most appropriate version of the method to call.

As the view class has already been set as the delegate for the navigation bar, it will be receiving all button click events that occur on it. This requires that the view class have a method named buttonClicked accepting the same arguments as if it were the navigation bar itself.

- (void)navigationBar:(UINavigationBar *)navbar
    buttonClicked:(int)button;

The buttonClicked method in the view class gets notified as if it belonged to the navigation bar. It is called with the same arguments that the navigation bar’s buttonClicked method would be called; a pointer to the navigation bar and an integer identifying the button number that was pressed. When using left and right navigation buttons, the value for button is either a 1 or a 0, signifying the left or right button, respectively.

- (void)navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button
{
    switch(button) {
        case 1:
            /* Left button handled here */
            break;
        case 0:
            /* Right button handled here */
            break;
    }
}

Disabling Buttons

If at any point you would like to disable an existing navigation bar button, you have two options. You can make another call to one of the show-button methods (showLeftButton or showButtonsWithLeftTitle) using nil to make the button disappear. Alternatively, if you would like the button to remain visible, but be disabled so that the user can’t press it, use the setButton method:

[ navBar setButton: 0 enabled: NO ];

Just as in the buttonClicked method, the button number used here corresponds with either the left (1) or the right (0) button.

Adding a Segmented Control

Controls are small, self-contained user interface components that can be used by various UIKit classes. They can be glued to many different types of objects, allowing the developer to add additional interaction to a window. One common control found in the navigation bars of Apple’s preloaded applications is the segmented control.

You’ll notice in many preloaded applications that Apple has added buttons to further separate the display of information. For example, the navigation bar in the iTunes WiFi Store application sports “New Releases,” “What’s Hot,” and “Genres” buttons at the top. These further separate the user’s music selection choice. Segmented controls are useful for any such situation where an overabundance of data would best be organized into separate tabs.

An example of setting up such a control with two segments follows:

UISegmentedControl *segCtl = [ [ UISegmentedControl alloc ]
    initWithFrame:CGRectMake(70.0, 8.0, 180.0, 30.0)
    withStyle: 2
    withItems: NULL ];
[ segCtl insertSegment:0 withTitle:@"All" animated: TRUE ];
[ segCtl insertSegment:1 withTitle:@"Missed" animated: TRUE ];
[ segCtl setDelegate:self ];

The control is then added to the navigation bar in the same way the navigation bar was added to the main view, by adding it as a subview. Only this time, the segmented control is added to the navigation bar. This means the offset of the display region is in relation to the navigation bar, not the enclosing view or the window.

[ navBar addSubview: segCtl ];

Each button in a segmented control is referred to as a segment. The selected segment can be accessed with a call to the selectedSegment method of the control itself.

int selectedSegment = [ segCtl selectedSegment ];

Chapter 7 offers more on controls.

Example: Mute Button for the Spouse

Someone is designing a mute button to mute their spouse during arguments. In this example application, a navigation bar will be created in an application’s view and assigned a Mute button. When the button is selected, the button will be changed to red and will also change the title to reflect that the spouse is muted.

Example 3-7 and Example 3-8 can be built using the tool chain on the following command line:

$ arm-apple-darwin-gcc -o MyExample MyExample.m -lobjc 
    -framework CoreFoundation -framework Foundation -framework UIKit
Example 3-7. Navigation bar example (MyExample.h)
#import <CoreFoundation/CoreFoundation.h>
#import <UIKit/UIKit.h>
#import <UIKit/UINavigationBar.h>
#import <UIKit/UINavigationItem.h>

@interface MainView : UIView
{
        UINavigationBar  *navBar;     /* Our navigation bar */
        UINavigationItem *navItem;    /* Navigation bar title */
        BOOL              isMuted;    /* Is mute turned on? */
}

- (id)initWithFrame:(CGRect)frame;
- (void)dealloc;
- (UINavigationBar *)createNavBar:(CGRect)rect;
- (void)setNavBar;
- (void)navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button;
@end

@interface MyApp : UIApplication
{
    UIWindow *window;
    MainView *mainView;
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
@end
Example 3-8. Navigation bar example (MyExample.m)
#import "MyExample.h"

int main(int argc, char **argv)
{
    NSAutoreleasePool *autoreleasePool = [
        [ NSAutoreleasePool alloc ] init
    ];
    int returnCode = UIApplicationMain(argc, argv, [ MyApp class ]);
    [ autoreleasePool release ];
    return returnCode;
}

@implementation MyApp

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    window = [ [ UIWindow alloc ] initWithContentRect:
        [ UIHardware fullScreenApplicationContentRect ]
    ];

    CGRect rect = [ UIHardware fullScreenApplicationContentRect ];
    rect.origin.x = rect.origin.y = 0.0f;

    mainView = [ [ MainView alloc ] initWithFrame: rect ];

    [ window setContentView: mainView ];
    [ window orderFront: self ];
    [ window makeKey: self ];
    [ window _setHidden: NO ];
}
@end

@implementation MainView
- (id)initWithFrame:(CGRect)rect {
    if ((self == [ super initWithFrame: rect ]) != nil) {
        isMuted = NO;

        navBar = [ self createNavBar: rect ];

        /* Update the navBar for the first time */
        [ self setNavBar ];

        [ self addSubview: navBar ];
    }

    return self;
}

- (void)dealloc
{
    [ navBar release ];
    [ navItem release ];
    [ self dealloc ];
    [ super dealloc ];
}

- (UINavigationBar *)createNavBar:(CGRect)rect {
    UINavigationBar *newNav = [ [UINavigationBar alloc]
      initWithFrame:
      CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, 48.0)
    ];

    [ newNav setDelegate: self ];
    [ newNav enableAnimation ];

    /* Add our title */
    navItem = [ [UINavigationItem alloc]
        initWithTitle:@"My Example" ];
    [ newNav pushNavigationItem: navItem ];

    return newNav;
}

- (void)setNavBar
{
    if (isMuted == YES) {
        [ navItem setTitle: @"Spouse (Muted)" ];
        [ navBar showLeftButton:nil withStyle: 0
                   rightButton:@"Mute" withStyle: 1 ];
    } else {
        [ navItem setTitle: @"Spouse" ];
        [ navBar showLeftButton:nil withStyle: 0
                    rightButton:@"Mute" withStyle: 0 ];
    }
}

- (void)navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button
{
    if (button == 0) /* Right button */
    {
        if (isMuted == YES) /* Toggle isMuted */
            isMuted = NO;
        else
            isMuted = YES;

        [ self setNavBar ]; /* Update navbar buttons */
    }
}

@end

What’s Going On

This example shows you how to accomplish the user-interface portion of this application. You’ll have to write the source code to do the actual spouse muting yourself. Here’s how the example works:

  1. The application instantiates through the main( ) function and returns an instance of the application, just as in previous examples.

  2. A window is created with mainView as the content. The statement creating the MainView object also calls its initWithFrame method. This creates the view and navigation bar, and sets the value for isMuted to NO so that the application starts out in an unmuted state.

  3. The initWithFrame method proceeds to call the setNavBar method to set up the navigation bar in whatever configuration is presently reflected by the state of isMuted. The first call sets up the buttons and title for the NO value.

  4. When the user taps on mute, the buttonClicked method gets called in the view (the bar’s delegate). This toggles the isMuted variable and then calls setNavBar again, which resets the navigation bar’s configuration. This transitions the button to red (button style 1) and sets the title to include Muted in the text.

  5. If the user taps on the mute button again, the buttonClicked method is called again, resetting isMuted back to NO. It then updates the navigation bar once more with a call to setNavBar.

Further Study

With the example snippets from this section and the previous examples, try having a little fun with this example:

  • Try changing this code to support the Good and Evil buttons example, so that pressing the Good button will cause it to turn blue, and pressing the Evil button will cause it to turn red.

  • Take the UITextView code in Example 3-2 and add two buttons, one for HTML and one for Text. Tapping each button should change the text view to display the file in the corresponding format.

  • Check out the UINavigationBar.h and UINavigationItem.h prototypes in the tool chain’s include directory. These can be found in /usr/local/arm-apple-darwin/include/UIKit/. Experiment with some of the different methods available.

Transition Views

If there’s one thing Apple is well known for, it’s their devotion to aesthetics in their user interfaces. The effect of sliding pages left and right gives the user a sense of the flow of data through an application, or a sense of “forward” and “back.” Even applications lacking a book type of structure can appreciate the smooth slide and fade transitions offered by UIKit. Transition views are objects that allow the current view on the screen to be swapped out smoothly and replaced by another view, with very little programming on the developer’s part.

Creating a Transition

Transition views inherit from UIView, so they have most of the properties of a regular view, including a frame. To create a transition, the display region belonging to the view is passed to the transition’s initWithFrame method.

UITransitionView *transitionView = [ [ UITransitionView alloc ]
    initWithFrame: viewRect ];

The same transition view can be used for multiple transition calls and even multiple transition types, so generally only one transition view is needed for a particular display region. If a navigation bar is being used, you must account for its presence by subtracting its height and coordinates from the transition view’s display region:

UITransitionView *transitionView = [ [ UITransitionView alloc ]
     initWithFrame:
     CGRectMake(viewRect.origin.x, viewRect.origin.y + 48.0,
            viewRect.size.width, viewRect.size.height − 48.0
];

Once the transition has been created, it is added to the view in the same way as other view objects, as a sublayer.

[ self addSubview: transitionView ];

Calling a Transition

To effect a transition, call the class’s transition method. You’ll supply the transition style and pointers to the two views between which you are transitioning.

[ transitionView transition: 0
  fromView: myOldView
  toView: myNewView
];

Alternatively, a transition can be called supplying only the new view, not the old one. But be forewarned that this appears to work with only some transitions. Those that do not support this behavior automatically default to using no transition.

[ transitionView transition: 0 toView: myNewView ];

UIKit presently supports 10 distinct transitions. A single transition view can be called repeatedly with different styles, listed in the following table. This allows developers to choose the best transition depending on the particular action at hand, without having to worry about creating a new object for every possible transition they might want to perform.

Style

Description

0

No transition/instant transition

1

Pages scroll left

2

Pages scroll right

3

Pages scroll up

4

Old page peels up, new page peels down

5

Old page peels down, new page peels up

6

New page fades over old page

7

Pages scroll down

8

New page peels up over old page

9

New page peels down over old page

Example: Page Flipping

The best example of using page transitions is to illustrate reading a book. In this example, 10 pages of text are created using the UITextView object covered earlier in this chapter. Using a navigation bar, the user is presented with two buttons to navigate to the previous or next page. Depending on which direction the user has chosen, a different transition is used. When the user reached either end of the book, the corresponding navigation button is disabled to keep them from going any further.

Example 3-9 and Example 3-10 can be compiled using the tool chain on the following command line:

$ arm-apple-darwin-gcc -o MyExample MyExample.m -lobjc 
    -framework CoreFoundation -framework Foundation -framework UIKit
Example 3-9. Page-flipping example (MyExample.h)
#import <CoreFoundation/CoreFoundation.h>
#import <UIKit/UIKit.h>
#import <UIKit/UINavigationBar.h>
#import <UIKit/UINavigationItem.h>
#import <UIKit/UITransitionView.h>
#import <UIKit/UITextView.h>

#define MAX_PAGES 10

@interface MainView : UIView
{
    UINavigationBar    *navBar;    /* Our navigation bar */
    UINavigationItem   *navItem;   /* Navigation bar title */
    UITransitionView   *transView; /* Our transition */
    int                pageNum;    /* Current page number */

    /* Some pages to scroll through */
    UITextView         *textPage[MAX_PAGES];
}

- (id)initWithFrame:(CGRect)frame;
- (void)dealloc;
- (UINavigationBar *)createNavBar:(CGRect)rect;
- (void)setNavBar;
- (void)navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button;
- (void)flipTo:(int)page;

@end

@interface MyApp : UIApplication
{
    UIWindow *window;
    MainView *mainView;
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
@end
Example 3-10. Page-flipping example (MyExample.m)
#import "MyExample.h"

int main(int argc, char **argv)
{
    NSAutoreleasePool *autoreleasePool = [
        [ NSAutoreleasePool alloc ] init
    ];
    int returnCode = UIApplicationMain(argc, argv, [ MyApp class ]);
    [ autoreleasePool release ];
    return returnCode;
}

@implementation MyApp

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    window = [ [ UIWindow alloc ] initWithContentRect:
        [ UIHardware fullScreenApplicationContentRect ]
    ];

    CGRect rect = [ UIHardware fullScreenApplicationContentRect ];
    rect.origin.x = rect.origin.y = 0.0f;

    mainView = [ [ MainView alloc ] initWithFrame: rect ];

    [ window setContentView: mainView ];
    [ window orderFront: self ];
    [ window makeKey: self ];
    [ window _setHidden: NO ];
}
@end

@implementation MainView
- (id)initWithFrame:(CGRect)rect {
    if ((self == [ super initWithFrame: rect ]) != nil) {
        CGRect viewRect;
        int i;

        /* Create a new view port below the navigation bar */
        viewRect = CGRectMake(rect.origin.x, rect.origin.y + 48.0,
            rect.size.width, rect.size.height - 48.0);

        /* Set our start page */
        pageNum = MAX_PAGES / 2;

        /* Create ten UITextView objects as pages in our book */

        for(i=0;i<MAX_PAGES;i++) {
            textPage[i] = [ [ UITextView alloc ]
                initWithFrame: rect ];
            [ textPage[i] setText: [ [ NSString alloc ]
                initWithFormat: @"Some text for page %d", i+1 ] ];
        }

        /* Create a navigation bar with 'Prev' and 'Next' buttons */
        navBar = [ self createNavBar: rect ];
        [ self setNavBar ];
        [ self addSubview: navBar ];

        /* Create our transition view */
        transView = [ [ UITransitionView alloc ]
                initWithFrame: viewRect ];
        [ self addSubview: transView ];

        /* Transition to the first page */
        [ self flipTo: pageNum ];
    }

    return self;
}

- (void)dealloc
{
    [ navBar release ];
    [ navItem release ];
    [ self dealloc ];
    [ super dealloc ];
}

- (UINavigationBar *)createNavBar:(CGRect)rect {
    UINavigationBar *newNav = [ [UINavigationBar alloc]
      initWithFrame:
      CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, 48.0)
    ];

    [ newNav setDelegate: self ];
    [ newNav enableAnimation ];

    /* Add our title */
    navItem = [ [UINavigationItem alloc]
        initWithTitle:@"My Example" ];
    [ newNav pushNavigationItem: navItem ];

    [ newNav showLeftButton:@"Prev"  withStyle: 0
                rightButton:@"Next" withStyle: 0 ];

    return newNav;
}

- (void)setNavBar
{

    /* Enable or disable our page buttons */

    if (pageNum == 1)
        [ navBar setButton: 1 enabled: NO ];
    else
        [ navBar setButton: 1 enabled: YES ];

    if (pageNum == MAX_PAGES)
        [ navBar setButton: 0 enabled: NO ];
    else
        [ navBar setButton: 0 enabled: YES ];
}

- (void)navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button
{
    /* Next Page */
    if (button == 0)
    {
        [ self flipTo: pageNum+1 ];
    }

    /* Prev Page */
    else {
        [ self flipTo: pageNum−1 ];
    }

}

- (void)flipTo:(int)page {
    int transitionNum; /* What transition number should be used? */

    if (page < pageNum)
        transitionNum = 2;
    else if (page > pageNum)
        transitionNum = 1;
    else
        transitionNum = 0;

    [ transView transition: transitionNum
      fromView: textPage[pageNum−1] toView: textPage[page−1] ];

    pageNum = page;

    [ self setNavBar ];
}

@end

What’s Going On

Here’s how the page-flipping example works:

  1. When the application instantiates, a MainView object is created and its initWithFrame method is called. This creates ten UITextView objects to serve as reading page examples, and then creates the navigation bar and one transition object. Finally, an example method named flipTo is called, which is responsible for flipping to the page number specified.

  2. The flipTo method decides on a transition style, based on whether the page being flipped to is the next page or the previous one.

  3. After deciding which transition to use, the flipTo method calls the transition view to do the work of moving to the desired page. It then sets the active page number, which is a variable in the class.

  4. When the user presses the Prev or Next navigation buttons, the buttonClicked method gets called with a pointer to the navigation bar and the button number. From here, the flipTo method is called again to transition to the new page and disable either navigation button if it has reached one end of the book.

Further Study

Here are some productive things you can do with the page-flipping example:

  • Experiment with the different transition styles available and think of actions where you would want to use each transition.

  • Use the page-flip example and fill in each box with a frame from an ASCII doodle. Now, write a rapid transition across all 10 pages to create an ASCII animation that acts like an old style movie projector.

  • Check out the UITransitionView.h prototypes in your tool chain’s include directory. You’ll find it in /usr/local/arm-apple-darwin/include/UIKit/.

Alert Sheets

The iPhone is a relatively small device with limited screen space and no stylus. This means users are going to fumble with their fingers and tap buttons on accident. When this happens, a well-written application prompts the user for confirmation before just blowing away important data. On a desktop computer, applications pop up windows when they need attention. On the iPhone, an alert sheet is slid up from the bottom, graying out the rest of the screen until the user chooses an option. The term “sheet” continues the page metaphor that Apple uses for the iPhone.

Creating an Alert Sheet

The alert sheet is an object that can be instantiated on top of any view, and a single view can host a number of different alert sheets. A basic alert sheet consists of a title, body text, and whatever choices the user should be presented with. It is instantiated in the same way as other UIView objects, using an initWithFrame method. Because alert sheets appear at the bottom, the window’s origin can begin halfway down the screen (Y = 240).

UIAlertSheet *alertSheet = [ [ UIAlertSheet alloc ]
    initWithFrame: CGRectMake(0, 240, 320, 240) ];
[ alertSheet setTitle:@"Computer doesn't like people" ];
[ alertSheet setBodyText: [ NSString stringWithFormat:
    @"I did not complete your request because I don't like humans." ]
];
[ alertSheet setDelegate: self ];

Alert sheets can stretch their frame to accommodate whatever elements it’s required to hold, so a static 320×240 rectangle should be sufficient for standard alert sheets.

Like navigation bars, alert sheets can be set to support one of three different styles.

Style

Description

0

Default style, gray gradient background with white text

1

Solid black background with white text

2

Transparent black background with white text

The style can be set with a call to setAlertSheetStyle.

[ alertSheet setAlertSheetStyle: 0 ];

Alert Sheet Buttons

On rare occasion, it makes sense to display an alert sheet without buttons—for example, to display a progress bar (explained in Chapter 7). In most cases, however, alert sheets are displayed for the purpose of prompting the user. An alert sheet can accommodate as many buttons as can fit on the screen. To add a button, call the alert sheet’s addButtonWithTitle method:

[ alertSheet addButtonWithTitle:@"OK" ];

Destructive buttons

Buttons that confirm permanent deletion or some other action that could result in data being destroyed should use what the API refers to as a destructive button. Destructive buttons appear in bright red to alert the user that they are about to perform a significant action that cannot be undone.

The setDestructiveButton method is used to mark a button as destructive. It accepts a button object as its input, and because addButtonWithTitle returns a pointer to the new button, the setDestructiveButton method can be wrapped around it.

[ alertSheet setDestructiveButton: [ alertSheet
               addButtonWithTitle:@"Confirm Delete" ] ];

Displaying the Alert Sheet

Once you’ve set up the text and buttons to be displayed, you can then display the sheet to the user. Five different methods can be called depending on the behavior desired. Each of these methods accepts a class derived as a UIView, which should be a pointer to whatever view class affected by the alert sheet.

[ alertSheet presentSheetFromAboveView: myView ];
[ alertSheet presentSheetFromBehindView: myView ];
[ alertSheet presentSheetFromButtonBar: buttonBar ];
[ alertSheet presentSheetInView: myView ];
[ alertSheet presentSheetToAboveView: myView ];

The most common way to call an alert sheet is with presentSheetInView, which can be called from within a view using self as an argument.

Intercepting Button Presses

When the alert sheet has been displayed, control returns to the program. As seen earlier in the chapter, many objects use a callback method named buttonClicked whenever the user taps a button. This allows the application to continue running in the background, to be interrupted only when something actually happens.

The prototype for the alert sheet callback method is below. If the alert sheet’s delegate has been set to the calling view, it is the calling view that will be expected to respond to a button click.

- (void)alertSheet:(UIAlertSheet *)sheet buttonClicked:(int)button;

The buttonClicked method is called with a pointer to the selected sheet and the index of the button. Buttons are numbered from topmost position to bottom, beginning with 1 (they are not zero-indexed like other values).

- (void)alertSheet:(UIAlertSheet *)sheet buttonClicked:(int)button {
    if (sheet == alertSheet) {
        switch(button) {
            case 1:
                /* Top-most button was clicked */
                break;
            case 2:
                /* Second-to-top button was clicked */
                break;
        }
    }
}

Dismissing an Alert Sheet

Finally, after processing a button press, the alert sheet should vanish—unless, of course, the application has a reason for the user to press more than one button. Use the dismiss method to make the sheet go away:

[ sheet dismiss ];

Example: End-of-the-World Button

The government thought it would be more convenient for the President to carry an iPhone around instead of a suitcase with a big red button. One of their chief programmers conveniently wrote End-of-the-World.app, which the President can press at any time to launch nukes and end the world (or at least start a pie fight). The problem, however, is that he’s almost pressed it accidentally many times. You’ve been contracted to add a confirmation sheet to it—just in case the President didn’t really mean to end the world.

Our example application displays a navigation bar with an End World button. When pressed, it will first confirm before ending the world as we know it. Polymorphism will be illustrated with the presence of two buttonClicked methods. When the navigation bar’s button is pressed, the correct buttonClicked method will be automatically called.

Example 3-11 and Example 3-12 can be built using the tool chain on the following command line:

$ arm-apple-darwin-gcc -o MyExample MyExample.m -lobjc 
    -framework CoreFoundation -framework Foundation -framework UIKit
Example 3-11. Alert sheet example (MyExample.h)
#import <CoreFoundation/CoreFoundation.h>
#import <UIKit/UIKit.h>
#import <UIKit/UIAlertSheet.h>
#import <UIKit/UINavigationBar.h>

@interface MainView : UIView
{
    UIAlertSheet    *endWorldSheet;
    UIAlertSheet    *deniedSheet;
    UINavigationBar *navBar;
}

- (id)initWithFrame:(CGRect)frame;
- (void)dealloc;
@end

@interface MyApp : UIApplication
{
    UIWindow *window;
    MainView *mainView;
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
@end
Example 3-12. Alert sheet example (MyExample.m)
#import "MyExample.h"

int main(int argc, char **argv)
{
   NSAutoreleasePool *autoreleasePool = [
        [ NSAutoreleasePool alloc ] init
    ];
    int returnCode = UIApplicationMain(argc, argv, [ MyApp class ]);
    [ autoreleasePool release ];
    return returnCode;
}

@implementation MyApp

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    window = [ [ UIWindow alloc ] initWithContentRect:
        [ UIHardware fullScreenApplicationContentRect ]
    ];

    CGRect rect = [ UIHardware fullScreenApplicationContentRect ];
    rect.origin.x = rect.origin.y = 0.0f;

    mainView = [ [ MainView alloc ] initWithFrame: rect ];

    [ window setContentView: mainView ];
    [ window orderFront: self ];
    [ window makeKey: self ];
    [ window _setHidden: NO ];
}
@end

@implementation MainView
- (id)initWithFrame:(CGRect)rect {
    if ((self == [ super initWithFrame: rect ]) != nil) {

        /* Create a button to end the world */

        navBar = [ [UINavigationBar alloc] initWithFrame:
            CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, 48.0f)
        ];
        [ navBar setDelegate: self ];
        [ navBar enableAnimation ];
        [ navBar showLeftButton:nil withStyle: 0
                    rightButton:@"End World" withStyle: 1 ];

        [ self addSubview: navBar ];
    }

    return self;
}

- (void)alertSheet:(UIAlertSheet *)sheet buttonClicked:(int)button
{
    if (sheet == endWorldSheet) {
        if (button == 1) {

            /* Oops, Access Denied */
            deniedSheet = [ [ UIAlertSheet alloc ] initWithFrame:
                CGRectMake(0, 240, 320, 240)
            ];
            [ deniedSheet setTitle: @"Access Denied" ];
            [ deniedSheet setBodyText: @"Sorry, you must be super-user to
end the world" ];
            [ deniedSheet addButtonWithTitle:@"OK" ];
            [ deniedSheet setDelegate: self ];
            [ deniedSheet presentSheetInView: self ];
        }
    }

    [ sheet dismiss ];
}

- (void)navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button
{
    /* Ask about the end of the world */

    endWorldSheet = [ [ UIAlertSheet alloc ] initWithFrame:
        CGRectMake(0, 240, 320, 240)
    ];
    [ endWorldSheet setTitle: @"Please Confirm" ];
    [ endWorldSheet setBodyText:@"I noticed you are trying to end the world.
Are you sure you want to do this?" ];
    [ endWorldSheet setDestructiveButton:
        [ endWorldSheet addButtonWithTitle:@"End World" ]
    ];
    [ endWorldSheet addButtonWithTitle:@"Cancel" ];
    [ endWorldSheet setDelegate: self ];
    [ endWorldSheet presentSheetInView: self ];

}


- (void)dealloc
{
    [ self dealloc ];
    [ super dealloc ];
}

@end

What’s Going On

The process flow for this program works in the following fashion.

  1. When the application is instantiates, a MainView object is created and its initWithFrame method is called. This creates the view and navigation bar and displays them to the user.

  2. When the user presses the End World button, the object notifies its delegate’s buttonClicked method. Because the navigation bar object is expecting to pass a pointer to itself, the buttonClicked method that accepts a UINavigationBar * parameter is the one that gets called (based on the rules of polymorphism).

  3. The buttonClicked method creates an alert sheet called endWorldSheet and presents it to the user. It immediately returns control, but causes the contents of the view to be dimmed and inaccessible to the user.

  4. When the user presses a button on the alert sheet, the alert sheet object notifies its delegate’s buttonClicked method. Because the alert sheet class passes a UIAlertSheet * parameter, its corresponding version of buttonClicked is called.

  5. The method compares the pointer passed to it with the pointer to endWorldSheet. If they are the same pointer, the method knows that a button in that sheet was pressed. It then looks at the index number of the button and takes the appropriate action.

  6. If the user confirmed by pressing End World, the buttonClicked method itself creates and displays a new alert sheet informing the user that an error has occurred.

  7. When the user presses the OK button, the same buttonClicked method is called again, only this time, the pointer of the alert sheet passed in will be that of deniedSheet. The method summarily ignores this and simply dismisses the sheet without taking any further action.

Further Study

Play around with alert sheets for a bit to get a feel for how they work:

  • Experiment with adding different buttons to the alert sheet. How many buttons will fit on the screen? How much text can they hold?

  • Create an alert sheet with no buttons—one that informs the user a file is loading. Use an NSTimer to wait 10 seconds and then dismiss the sheet without using the buttonClicked method at all.

  • Check out the UIAlertSheet.h prototypes in your tool chain’s include directory. You’ll find it in /usr/local/arm-apple-darwin/include/UIKit/. Experiment with some of the less-documented methods.

Tables

Tables are the foundation for most types of selectable lists on the iPhone. Voicemail, recent calls, and even email all use the feature-rich UITable class to display their lists of items. In addition to being a basic list selector, the UITable class includes built-in functionality to add disclosures, swipe-to-delete, animations, labels, and even images.

Creating the Table

A table has three primary components: the table itself, table columns, and table cells (the individual rows in a table). The table’s data is queried from a table’s data binding. A data binding is an interface used by the table to query information about what data to display, such as filenames, email messages, etc. The data source is an object that responds to this query. When the table is created, a data source must be attached to it in order for the table to display anything. It gets called whenever the table is reloaded or new cells are scrolled into view and tells the table which columns and rows to display, along with the data within them.

Subclassing UITable

For most specialized uses, the table can serve as its own data source. This allows the table class and the table’s data to be wrapped cleanly into a single class. To do this, subclass the UITable object to create a new class for your data. In the following example, a subclass named MyTable is created. The base class methods used to initialize and destroy the object are overridden to provide the table portion of the class:

@interface MyTable : UITable
{

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

To add the data source portion of the class, two methods are used to make the table’s data binding load data: numberOfRowsInTable and cellForRow. Because the table is acting as its own data source, you must write these methods into your subclass, where the methods will be responsible for returning column and row data for the table.

- (int)numberOfRowsInTable:(UITable *)_table;
- (UITableCell *)table:(UITable *)table
    cellForRow:(int)row
    column:(UITableColumn *)col;

We’ll look at these methods later in the section "Data binding.”

Overriding UITable methods

When creating a subclass of UITable, the initialization and destructor methods should be overridden. This enables the subclass to define its own columns and style when it’s created, and properly release any resources it creates for itself.

The initialization method for a UITable object is initWithFrame:

- (id)initWithFrame:(struct CGRect)rect {
    if ((self == [ super initWithFrame: rect ]) != nil) {

        /* Add additional initialization code here */
    }
}

Because the table is acting as its own data source, the initWithFrame method can be used to define the table’s columns. A column is created as a UITableColumn class and has its own title, identifier, and width inside the table. The UITableColumn object is also used by many derivative classes such as pickers, discussed in Chapter 7.

        UITableColumn *myColumn = [ [ UITableColumn alloc ]
            initWithTitle: @"Column 1"
            identifier:@"column1"
            width: rect.size.width
        ];
        [ self addTableColumn: myColumn ];

To create a self-contained table class, the UITable object can be designated as its own data source. Issue setDataSource to bind self as the data source.

        [ self setDataSource: self ];

Override the dealloc method so you can add code to free any resources that should be disposed of when the object is destroyed. When a MyTable object is disposed of, the dealloc method should release its columns and any other resource it has allocated. It will also need to call its superclass’s dealloc method, to free any resources created internally by the UITable class.

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

Data binding

The data binding for a UITable consists of two methods to provide the row data for the table, as mentioned earlier in the section "Subclassing UITable.” The numberOfRowsInTable method simply returns an integer reflecting the number of rows of data for the table. This value is used by the table object to set the number of rows.

- (int)numberOfRowsInTable:(UITable *)_table {
    return 3;
}

The second method, cellForRow, returns the individual rows of the table. It is called for every row whenever the row is brought into view. Individual rows are derived from the UITableCell class. The following example defines a cellForRow method that creates a cell from the UITableCell class, sets its title based simply on the row number, and returns the cell.

- (UITableCell *)table:(UITable *)table
  cellForRow:(int)row
  column:(UITableColumn *)col
{
    UISimpleTableCell *cell = [ [ UISimpleTableCell alloc ] init ];
    NSString *title;

    title = [ [ NSString alloc ] initWithFormat: @"Row %d", row ];
    [ cell setTitle: title ];
    return [ cell autorelease ];
}

This simple cell is a text-only cell containing a title, separator, and optional disclosure (described shortly). Because these properties are set for each row cell, any cell in a table can be specially formatted to meet the needs of the data.

Labels

Labels are miniature view classes that can be added to table cells to further augment the table cell with decorated text. Different classes of labels exist for different purposes. For instance, a web view label appears as a gray transparent oval containing text. In the following example, a UIWebViewLabel class is created with offsets that will cause it to appear in the upper-left corner of the cell.

UIWebViewLabel *label = [ [ UIWebViewLabel alloc ] initWithFrame:
    CGRectMake(0.0, 3.0f, 320.0, 20.0) ];
[ label setText: @"My UIWebView" ];
[ cell addSubview: label ];

The following label classes are available.

Class

Description

UITextLabel

Displays simple text in its view region

UITextLabelField

Provides a text entry window where users can enter text

UIDateLabel

Displays a date in its view region

UIWebViewLabel

Displays text within a gray transparent surface

Disclosures

Disclosures are icons appearing at the right side of a table cell to disclose that there is another level of information to be displayed when the cell is selected. These are commonly used on desktop in interfaces such as iTunes, where the user first selects a genre, then artist, and finally, a song.

To enable the disclosure for a particular table cell, use the cell’s setShowDisclosure method:

[ cell setShowDisclosure: YES ];

The disclosure style can also be changed:

[ cell setDisclosureStyle: 0 ];

Two disclosure styles are available.

Style

Description

0

Black arrow

1

Blue circle with white arrow

Image and text cells

Tables can display images next to row text. This requires the use of a different type of table cell named UIImageAndTextTableCell. In the example MyTable class, this type of object would be returned instead of a UISimpleTableCell in the cellForRow method.

UIImageAndTextTableCell *cell =
    [ [ UIImageAndTextTableCell alloc ] init ];
UIImageView *image = [ [ UIImage alloc ]
    initWithContentsOfFile: @"/path/to/file.png" ];
[ cell setTitle: @"My row, now with image!" ];
[ cell setImage: image ];
 return [ cell autorelease ];

The UIImage class is used to load an image file. This class is further explained in Chapter 7.

This type of data cell also allows the image and text alignment to be changed using a method called setAlignment.

[ cell setAlignment: 2 ];

The following alignment styles are supported.

Style

Description

0

Image and text left-aligned with margin separator

1

Image and text left-aligned with no margin separator

2

Image left-aligned, text centered

3

Image left-aligned, text right-aligned

Depending on the size of the images that will be loaded, the row height may need to be changed to accommodate the images. The setRowHeight method is part of the UITable base class, and can be set when the table is initialized:

[ self setRowHeight: 64 ];

Because this is a table-level setting, the row height will affect all cells equally.

Swipe-to-delete

The UITable object has built-in logic to intercept swipe gestures and display delete confirmations. This allows the user to swipe his finger across a row he’d like to delete.

To intercept swipe gestures, override the swipe method in the subclass you derive fom UITable.

- (int)swipe:(int)type withEvent:(struct _  _GSEvent *)event;
{
    CGPoint point = GSEventGetLocationInWindow(event);
    CGPoint offset = _startOffset;

    point.x += offset.x;
    point.y += offset.y;
    int row = [ self rowAtPoint:point ];

    [ [ self visibleCellForRow: row column:0 ]
       _showDeleteOrInsertion: YES
       withDisclosure: NO
       animated: YES
       isDelete: YES
       andRemoveConfirmation: YES
    ];

    return [ super swipe:type withEvent:event ];
}

The swipe method is passed a gesture event, which is handled by a framework named Graphics Services (covered in Chapter 4). Calling this framework’s GSEventGetLocationInWindow method will determine the point on the screen where the swipe occurred. Because the point on the screen (and not within the window) is returned, the offset of the window’s position on the screen must be taken into account. For example, if the window appears below a 48-point navigation bar, then the vertical offset must take this into account. In this example, the _startOffset variable is used to offset screen coordinates to window coordinates.

Once the screen coordinates of the swipe been determined, the row number is then found by handing the coordinates to the rowAtPoint method belonging to the UITable class. This returns the row number as an integer.

Next, find the table cell itself using UITable’s visibleCellForRow method. This will return a pointer to the selected table cell, whose _showDeleteOrInsertion method can be invoked to display the delete confirmation. This appears as a red delete button.

To disable the user’s ability to delete rows from the table, this method can just return without taking any action.

When the user confirms that she wants to delete the row (by pressing the delete button), an internal method named _willDeleteRow is called. When a row is deleted, the row must be removed from the data source; otherwise, it will appear again should the user scroll it off the screen and back. There may also be additional operations the application will need to perform, such as deleting a file or message associated with the cell.

- (void)_willDeleteRow:(int)row
    forTableCell:(id)cell
    viaEdge:(int)edge
    animateOthers:(BOOL)animate
{
    /* Perform any additional deletion operations here */

    [ fileList removeObjectAtIndex: row ];
    [ super _willDeleteRow: row
        forTableCell: cell
        viaEdge: edge
        animateOthers: animate
    ];
}

Item selection

When the user selects a table cell, the table’s tableRowSelected method is called. This method should be overridden to act on the selection, for example, when opening a file or launching an application. The row number can be accessed using the base class’s selectedRow method.

- (void)tableRowSelected:(NSNotification *)notification
{
    int index = [ self selectedRow ];

    /* A file was selected. Do something here. */
}

Once the row’s index is found, as shown above, you can use it to reference the actual row value from your own data and act on it appropriately. For example, if your table is an array of files, then you would use the index to reference the correct filename in your array. From there, you can load the file or perform whatever other operations your application is designed for.

Example: File Browser

In this example, we create a custom UITable class for general use as a file browser. The browser reads a directory supplied by caller, using its setPath method, and displays all of the files downwind of it matching a file extension also supplied by the caller, using its setExtension method. The swipe-to-delete functionality has been added for the sake of the example, but (in order to prevent you from trashing files by mistake while experimenting with the example) it deletes the item only from the list, not from the iPhone’s filesystem.

This example can be easily added to any iPhone application to display lists of files by including the following code in an application’s view class:

#import "FileTable.h"

FileTable *fileTable = [ [ FileTable alloc ] initWithFrame: rect ];
[ fileTable setPath: @"/Applications" ];
[ fileTable setExtension: @"app" ];
[ fileTable reloadData ];
[ self addSubview: fileTable ];

A complete application, shown in Example 3-13 through Example 3-18, can be compiled from the tool chain using the following command line:

$ arm-apple-darwin-gcc -o MyExample MyExample.m FileTable.m DeletableCell.m 
    -lobjc -framework CoreFoundation -framework Foundation -framework UIKit
Example 3-13. Application class header (MyExample.h)
#import <CoreFoundation/CoreFoundation.h>
#import <UIKit/UIKit.h>
#import "FileTable.h"

@interface MainView : UIView
{
        FileTable *fileTable;
}
- (id)initWithFrame:(CGRect)frame;
- (void)dealloc;
@end

@interface MyApp : UIApplication
{
    UIWindow *window;
    MainView *mainView;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
@end
Example 3-14. Application class implementation (MyExample.m)
#import "MyExample.h"

int main(int argc, char **argv)
{
    NSAutoreleasePool *autoreleasePool = [ [ NSAutoreleasePool alloc ] init ];
    int returnCode = UIApplicationMain(argc, argv, [ MyApp class ]);
    [ autoreleasePool release ];
    return returnCode;
}

@implementation MyApp

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    window = [ [ UIWindow alloc ] initWithContentRect:
        [ UIHardware fullScreenApplicationContentRect ]
    ];

    CGRect rect = [ UIHardware fullScreenApplicationContentRect ];
    rect.origin.x = rect.origin.y = 0.0f;

    mainView = [ [ MainView alloc ] initWithFrame: rect ];

    [ window setContentView: mainView ];
    [ window orderFront: self ];
    [ window makeKey: self ];
    [ window _setHidden: NO ];
}
@end

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

    if ((self == [ super initWithFrame: rect ]) != nil) {

        fileTable = [ [ FileTable alloc ] initWithFrame: rect ];
        [ fileTable setPath: @"/Applications" ];
        [ fileTable setExtension: @"app" ];
        [ fileTable reloadData ];
        [ self addSubview: fileTable ];
    }

    return self;
}

- (void)dealloc
{
    [ self dealloc ];
    [ super dealloc ];
}
@end
Example 3-15. File selector class headers (FileTable.h)
#import <UIKit/UIKit.h>
#import <UIKit/UISimpleTableCell.h>
#import <UIKit/UIImageAndTextTableCell.h>
#import <UIKit/UITableColumn.h>
#import <UIKit/UIImage.h>
#import <GraphicsServices/GraphicsServices.h>

@interface FileTable : UITable
{
    NSString *path;
    NSString *extension;
    NSMutableArray *fileList;
    UITableColumn *colFilename;
    UITableColumn *colType;
}
- (id)initWithFrame:(struct CGRect)rect;
- (void)setPath:(NSString *)_path;
- (void)setExtension:(NSString *)_extension;
- (void)reloadData;
- (int)swipe:(int)type withEvent:(struct _  _GSEvent *)event;
- (int)numberOfRowsInTable:(UITable *)_table;
- (UITableCell *)table:(UITable *)table
    cellForRow:(int)row
    column:(UITableColumn *)col;
- (void)_willDeleteRow:(int)row forTableCell:(id)cell viaEdge:(int)edge
animateOthers:(BOOL)animate;
- (void)dealloc;
@end
Example 3-16. File selector class implementation (FileTable.m)
#import "FileTable.h"
#import "DeletableCell.h"

@implementation FileTable

- (id)initWithFrame:(struct CGRect)rect {
    if ((self == [ super initWithFrame: rect ]) != nil) {

        colFilename = [ [ UITableColumn alloc ]
            initWithTitle: @"Filename"
            identifier:@"filename"
            width: rect.size.width - 75
        ];
        [ self addTableColumn: colFilename ];

        colType = [ [ UITableColumn alloc ]
            initWithTitle: @"Type"
            identifier:@"type"
            width: 75
        ];
        [ self addTableColumn: colType ];

        [ self setSeparatorStyle: 1 ];
        [ self setDelegate: self ];
        [ self setDataSource: self ];
        [ self setRowHeight: 64 ];

        fileList = [ [ NSMutableArray alloc] init ];
    }

    return self;
}

- (void) setPath:(NSString *)_path {
    path = [ _path copy ];
}

- (void) setExtension:(NSString *)_extension {
    extension = [ _extension copy ];
}

- (void) reloadData {
    NSFileManager *fileManager = [ NSFileManager defaultManager ];
    NSDirectoryEnumerator *dirEnum;
    NSString *file;

    if ([ fileManager fileExistsAtPath: path ] == NO) {
        return;
    }

    [ fileList removeAllObjects ];

    dirEnum = [ [ NSFileManager defaultManager ] enumeratorAtPath: path ];
    while ((file = [ dirEnum nextObject ])) {
        if ([ file hasSuffix: extension ] == YES) {
            [ fileList addObject: file ];
        }
    }

    [ super reloadData ];
}

- (int)numberOfRowsInTable:(UITable *)_table {
    return [ fileList count ];
}

- (UITableCell *)table:(UITable *)table
  cellForRow:(int)row
  column:(UITableColumn *)col
{
    if (col == colFilename) {
        DeletableCell *cell = [ [ DeletableCell alloc ] init ];
        [ cell setTable: self ];

        UIImageView *image = [ [ UIImage alloc ]
            initWithContentsOfFile:
            [ [ NSString alloc ]
               initWithFormat: @"/Applications/%@/icon.png",
                   [ fileList objectAtIndex: row ] ] ];
        [ cell setTitle: [ [ fileList objectAtIndex: row ]
        stringByDeletingPathExtension ]];
        [ cell setImage: image ];
        [ cell setShowDisclosure: YES ];
        [ cell setDisclosureStyle: 3 ];
        return [ cell autorelease ];
    } else if (col == colType) {
        DeletableCell *cell = [ [ DeletableCell alloc ] init ];
        [ cell setTable: self ];
        [ cell setTitle: extension ];
        return [ cell autorelease ];
    }
}

- (int)swipe:(int)type withEvent:(struct _  _GSEvent *)event;
{
    CGPoint point= GSEventGetLocationInWindow(event);
    CGPoint offset = _startOffset;

    if (point.x < 100 || point.x > 200) {
        point.x += offset.x;
        point.y += offset.y;
        int row = [ self rowAtPoint:point ];

        [ [ self visibleCellForRow: row column: 1 ]
           _showDeleteOrInsertion: YES
           withDisclosure: NO
           animated: YES
           isDelete: YES
           andRemoveConfirmation: YES
        ];

        return [ super swipe:type withEvent:event ];
    }
}

- (void)_willDeleteRow:(int)row
    forTableCell:(id)cell
    viaEdge:(int)edge
    animateOthers:(BOOL)animate
{
    [ fileList removeObjectAtIndex: row ];
    [ super _willDeleteRow: row forTableCell: cell viaEdge: edge
      animateOthers: animate ];
}

- (void)tableRowSelected:(NSNotification *)notification {
    NSString *fileName = [ fileList objectAtIndex: [ self selectedRow ] ];

    /* A file was selected. Do something here. */
}

- (void)dealloc {
    [ colFilename release ];
    [ colType release ];
    [ fileList release ];
    [ super dealloc ];
}
@end
Example 3-17. Deletable cell class header (DeletableCell.h)
#import "FileTable.h"

@interface DeletableCell : UIImageAndTextTableCell
{
    FileTable *table;
}
- (void)setTable:(FileTable *)_table;

@end
Example 3-18. Deletable cell class implementation (DeletableCell.m)
#import "DeletableCell.h"

@implementation DeletableCell

- (void)removeControlWillHideRemoveConfirmation:(id)fp8
{
    [ self _showDeleteOrInsertion:NO
          withDisclosure:NO
          animated:YES
          isDelete:YES
          andRemoveConfirmation:YES
    ];
}

- (void)_willBeDeleted
{
    int row = [ table _rowForTableCell: self ];

    /* Do something; this row is being deleted */
}

- (void)setTable:(FileTable *)_table {
    table = _table;
}
@end

What’s Going On

The FileTable class works like this:

  1. When the calling class allocates a new FileTable object, it calls its initWithFrame method.

  2. This causes two UITableColumn objects to be created, which serve as the table’s columns: Filename and Type. The class also sets itself as the data source and sets some aesthetic properties.

  3. The file table’s setPath and setExtension methods are used to set the desired path and extension for the files to display.

  4. The calling class calls the object’s reloadData method. This causes the file table to generate a list of files in the directory at the specified path with the extension specified. This list is stored in an array named fileList. The super class’s reloadData method is then called, which queries the number of rows from the data source through the numberOfRowsInTable method.

  5. As the file table is ready to display cells, it calls its data binding again to obtain data for each cell in view. The method’s cellForRow method is called for each column and row in view. The method returns the appropriate row cells as requested.

  6. If the user swipes across a row, the class’s swipe method is called. This displays a delete confirmation button by calling the cell’s visibleCellForRow method with the appropriate properties.

  7. If the user confirms deletion, the table cell’s _willDeleteRow method is called. This removes the row from the data source so that it will not be redisplayed should the cell be scrolled out of view, then back in.

Further Study

Try some of these exercises to test your knowledge of tables:

  • Incorporate this table into the “Hello, World!” example from the beginning of the chapter. Instead of displaying a UITextView object, build a FileTable object from this example. Be sure to #import the class’s header and include it in the source list when compiling.

  • Modify this example to add a method to enable or disable deletions. This should change the behavior of the swipe method so that it returns prematurely when deletion is disabled.

  • Use this example to display applications in the /Applications folder. Modify the example to use the UIImageAndTextTableCell object to display the application’s icon next to its name.

  • Check out the UITable.h, UISimpleTableCell.h, UIImageAndTextTableCell.h, and UITableColumn.h prototypes in your tool chain’s directory. These can be found in /usr/local/arm-apple-darwin/UIKit/.

  • Check out the various types of labels. UITextLabel.h, UIDateLabel.h, UITextFieldLabel.h, and UIWebViewLabel.h prototypes can be found in your tool chain’s directory. These can be found in /usr/local/arm-apple-darwin/UIKit/.

Status Bar Manipulation

The status bar’s appearance can be customized to meet the look and feel of your application, and can also display notifications about your application. For instance, the iPod application uses the status bar to display a triangular play icon when music is playing in the background. The alarm clock application displays a small clock on the status bar when an alarm is active. Many properties of the status bar can be changed using the UIApplication and UIHardware classes.

Status Bar Mode

The status bar mode determines its color, opacity, and orientation. It can also be used to animate the status bar when transitioning between different modes. To set the mode, one of four different setStatusBarMode methods can be used from within the instance of your application, a UIApplication object.

- (void)setStatusBarMode:(int)mode duration:(float)duration
- (void)setStatusBarMode:(int)mode orientation:(int)orientation
    duration:(float)duration
- (void)setStatusBarMode:(int)mode orientation:(int)orientation
    duration:(float)duration fenceID:(int)fenceID
- (void)setStatusBarMode:(int)mode orientation:(int)orientation
    duration:(float)duration fenceID:(int)fenceID
    animation:(int)animation;

Mode

The status bar mode sets the overall appearance in color and opacity. The following modes are supported.

Mode

Description

0

Default, white status bar

1

Black transparent status bar

2

Removes status bar image entirely (be sure to also use _setStatusBarSize)

3

Solid black status bar

4

Entirely transparent status bar

5

Flashing green status bar with “Touch to return to call” text

6

Red transparent status bar

Orientation

The orientation determines where the status bar is displayed on the iPhone’s screen. If the iPhone is being held in a landscape fashion, the application may choose to re-orient itself to accommodate the wider display. The value passed with the orientation argument represents the angle at which the iPhone’s screen will be displayed.

Angle

Description

0

Status bar is displayed at the natural top of the iPhone

90

Status bar is displayed in landscape across the right portion of the screen

−90

Status bar is displayed in landscape across the left portion of the screen

Duration

This specifies the number in seconds that an animated transition should take between the previous status bar state and the new one. Use the value 0 for an instantaneous transition.

Fence ID

Used internally. We have no idea what it means.

Animation

Sets the animation for transitioning from the previous status bar state to the new one. The following animations are presently supported:

Animation

Description

0

Fade out/fade in

1

New status bar enters screen from bottom

2

Old status bar exits screen from bottom

3

Old status bar exits screen from top; new status bar enters from top

4

New status bar enters screen from bottom using a different animation

5

No animation

Status Bar Size

If you’re planning on removing the status bar for a particular purpose, its size must be altered to release the screen space to other views. The status bar size can be set using the UIHardware class.

To remove the status bar entirely, set its height to zero:

[ UIHardware _setStatusBarHeight: 0.0 ];
[ self setStatusBarMode:2 duration: 0 ];

To restore it:

[ UIHardware _setStatusBarHeight: 20.0 ];
[ self setStatusBarMode:0 duration: 0 ];

Status Bar Images

Images can be placed on the status bar to notify the user that your application is in a particular state, such as playing music or being logged in to a chat room. Images remain on the status bar even when your application has exited, allowing the user to be notified of future events, such as an alarm. If your application exits prematurely, the image can be automatically removed.

Status bar images are controlled by the SpringBoard application. This makes for a slightly complicated setup in that your application will need to copy the images you use into SpringBoard’s program folder (and restart SpringBoard) before they will work.

Two files are needed to correctly display a status bar image: one for the white status bar and one for the black. Depending on what application is running, the status bar used will automatically switch to the appropriate image.

Installation

The first time your application runs, it should copy two files into the SpringBoard folder located at /System/Library/CoreServices/SpringBoard.app. Choose a one-word descriptor for your images and name them Default_NAME.png and FSO_NAME.png. The Default image will be displayed when the white status bar is used, and the FSO image will be shown on the black status bar.

After these files have been copied into the SpringBoard folder, the SpringBoard program will need to be restarted for them to be recognized. To do this, invoke launchctl in a shell on the iPhone:

# launchctl stop com.apple.SpringBoard

Alternatively, you can instruct the user to reboot his iPhone. If SpringBoard isn’t restarted, your status bar images will be ignored until the user reboots his iPhone, which may be acceptable.

Displaying and removing the status bar image

Once you’ve gotten everything set up, use the addStatusBarImageNamed method to display the new status bar using the one-word descriptor you came up with before.

[ UIApp addStatusBarImageNamed: @"NAME" removeOnAbnormalExit: YES ];

The removeOnAbnormalExit argument instructs the iPhone whether to automatically remove the image if your application crashes.

When you’re ready to remove the image by hand, use the removeStatusBarImageNamed method:

[ UIApp removeStatusBarImageNamed: @"NAME" ];

Application Badges

With the iPhone’s numerous different connections—EDGE, WiFi, and Bluetooth (not to mention the cellular network)—lots of things can happen while you’ve got that little device stuck in your pocket. Without some notification to the user that there are pending notifications, they’re likely to miss everything that’s happened while they were busy having a real life. Application badges are small message bubbles that appear on the program’s SpringBoard icon. Application badges are used heavily by Apple’s preloaded applications to alert the user to missed calls, voicemail, text messages, and email.

One of the nice features about these types of badges is that the application doesn’t necessarily need to be running for the badge to display on the SpringBoard. This is useful in serving as a reminder to the user even after they’ve exited the application. This also means you’ll need to clean up any lingering badges when your program exits.

Displaying an Application Badge

Application badges are one of the easier features to take advantage of, requiring only one call to the UIApplication class.

 [ UIApp setApplicationBadge: @"Hi!" ];

The setApplicationBadge method takes an NSString object, which can be built with standard string formatting.

NSString *badgeText = [ [ NSString alloc ]
    initWithFormat:@"%d", numNewMessages ];
[ UIApp setApplicationBadge: badgeText ];

Removing an Application Badge

An application badge should be removed when the user has clicked to the page with the important events they were being notified about. Removing the application badge is also an easy task. A good place to put such code is after transitioning to the view with the events. For example:

[ transitionView transition: 0 toView: missedCalls ];
[ UIApp removeApplicationBadge ];

An application badge will continue to hang around even after an application has terminated. This can be useful, but it’s not always what you want. If an application badge should be removed when the program exits, you’ll also need to make a call to removeApplicationBadge in the application’s applicationWillTerminate method.

- (void)applicationWillTerminate {
    /* We are about to exit, so remove the application badge */
    [ UIApp removeApplicationBadge ];
}

You’ll learn more about this kind of application state control in the next section.

Further Study

Before going on to learn some of the situations in which application badges can help improve your application’s response to state changes, do a little exploration:

  • Experiment and determine the maximum amount of text that can be added to an application badge. What happens when you exceed this limit?

  • Check out UIApplication.h in your tool chain’s include directory. You’ll find it in /usr/local/arm-apple-darwin/include/UIKit/. We’ll cover some more UIApplication methods in Chapter 7.

Application Services

The state of an application is more important on the iPhone than it is on the desktop. This is because many events on the iPhone can cause an application to suspend, run in the background, or terminate. These different states occur when the user presses the home button, locks the screen, or receives a phone call. It’s important for an application to know when its state is about to change to save any settings, halt threads, or perform other actions.

The UIApplication base class contains many functions for application state changes, which can be overridden by the application. While there’s nothing the application can generally do about the state it’s about to enter, it can at least take whatever actions are appropriate to prepare for it.

Suspending

When the home button is pressed, the default behavior for an application is to suspend. It’s also told to suspend during a phone call or when the screen is locked. When this happens, the application’s suspend methods are called.

Depending on the nature of the event, three different suspend methods might be called.

applicationWillSuspendUnderLock

The iPhone screen is locked either by pressing the power button or during an idle screen lock.

applicationWillSuspendForEventsOnly

Events that force the application into the background, such as receiving a phone call, cause this method to be called. Also, if the display is locked, but no applicationWillSuspendUnderLock method is overridden, this method gets called instead.

applicationSuspend

This method is called when the user presses the home button. It’s also the last chance for an application to perform any necessary actions. If neither of the other two methods have been overridden, this method gets called for all suspend events.

Because one method picks up the slack for others, most applications can service all suspend events by simply overriding applicationSuspend.

- (void)applicationSuspend:(struct _  _GSEvent *)event {
    /* We're about to suspend, so do something here */
}

Resources are always a concern with the iPhone, as memory and battery life are finite. If the application doesn’t need to run in the background and there’s no reason to keep the state of the application, it may make sense to just terminate instead of suspending. The iPhone is smart enough to restart the last application being used, so the user might not even know the difference.

- (void)applicationSuspend:(struct _  _GSEvent *)event {
    [ self terminate ];
}

It may also be appropriate to suspend or terminate depending on the state of the application. For example, if the application is an instant messaging client, it makes sense to suspend only if it’s got a live connection to a server, so that it continue running in the background and maintain the connection. If the user is logged out, there’s no reason to stay alive, and so terminating makes more sense.

- (void)applicationSuspend:(struct _  _GSEvent *)event {
    if (connected == NO) {
        [ self terminate ];
    }else {
        /* Set away in IM Client */
    }
}

When the application is suspended, it’s simply moved to the background and can continue to run. If a separate thread is running to check for new events, such as new instant messages, it is still able to communicate with the process and even send sound events to alert the user (discussed in Chapter 6).

Resuming

When an application is brought back to a run state, another set of methods are called to prepare the application after it has been brought back to life. These can be overridden to check (and reestablish) connectivity or perform other tasks.

The same three methods used earlier to handle different types of suspends also have complementary resume methods.

applicationDidResumeFromUnderLock

The application resumed from an iPhone whose screen was locked and powered off.

applicationDidResumeForEventsOnly

The application resumed after the end of a phone call or other event that forced it into the background.

applicationDidResume

Catch-all for all other resume events.

The resume methods are overridden in the same way as their suspend counterparts.

-   (void)applicationDidResume {

    /* We've resumed. Do something */
}

Program Termination

Unless an application terminates itself, it’s generally not terminated unless the iPhone is being shut down. Because most good applications do terminate themselves (rather than suspend), it’s a good idea to override the termination method just before an application terminates.

The applicationWillTerminate method is called whenever an application is about to be cleanly terminated. It is not called when an application is killed by holding down the home button. This method should perform any remaining cleanup of resources, make sure all connections are properly closed, and any other tasks necessary before the program exits.

- (void)applicationWillTerminate {
    /* We're about to exit, so do something */
}

Further Study

Here are some other ways you can play with the various application services:

  • Use a pthread or NSThread to create a background thread that maintains an active connection to a server. What happens to the connection when the application suspends?

  • Check out UIApplication.h in your tool chain’s include directory. You’ll find it in /usr/local/arm-apple-darwin/include/UIKit/. We’ll cover some more UIApplication methods in Chapter 7.

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

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