3.7. Action Sheets and Alerts

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 by accident. When this happens, a well-written application prompts the user for confirmation before destroying important data. On a desktop computer, applications pop up windows when they need attention. On the iPhone, a modal alert sheet slides 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. The iPhone does, in fact, also support pop-up windows in the form of alerts. These typically convey information, although they occasionally retrieve input as well.

3.7.1. Alerts

Alert views are the simplest of alerts supported by the iPhone. An alert view, represented by the UIAlertView class, is a small pop-up window appearing over an application window. Until the alert is dismissed, the rest of the application cannot be accessed:

UIAlertView *myAlert = [ [ UIAlertView alloc ]
   initWithTitle:@"Alert"
   message:@"This is an alert."
   delegate:self
   cancelButtonTitle: nil
   otherButtonTitles: @"OK", nil];

When the user presses a button in an alert window, the alert view notifies its delegate by invoking a method named clickedButtonAtIndex. This method belongs to the UIAlertViewDelegate protocol, which the delegate class must implement. To set the delegate class that will receive this notification, set the alert view's delegate property:

myAlert.delegate = self;

You'll then need to add this special method to your delegate class in order to handle the button press. An example follows:

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    NSLog(@"Button %d pressed", buttonIndex);
    [ alertView release ];
}

                                          

The clickedButtonAtIndex method is provided with a pointer to the UIAlertView object and a button index. The pointer can be compared with existing objects to determine in which alert view a button was pressed, allowing you to use a single delegate to handle multiple alerts. The buttonIndex parameter provides a zero-indexed button number corresponding to a button in the alert.

When you're ready to display the alert, use the UIAlertView class's show method:

[ myAlert show ];

3.7.2. Action Sheets

Action sheets are larger types of alerts that appear as "slide-up" pages over an existing view. A single view can host a number of different alert sheets, and handles them using the same type of delegate facilities as alert views. A basic alert sheet consists of a title, message, and whatever choices the user should be presented with. It is instantiated in the same way as an alert view:

UIActionSheet *mySheet = [ [ UIActionSheet alloc ]
    initWithTitle: @"Please Select"
    delegate: self
    cancelButtonTitle: @"Cancel"
    destructiveButtonTitle: @"Delete"
    otherButtonTitles: @"Move", @"Rename", nil ];

When the user presses a button, the delegate is notified by invoking its clickedButto⁠nAtIndex method. For action sheets, this method belongs to the UIActionSheetDele⁠gate protocol:

- (void)actionSheet:(UIActionSheet *)actionSheet
    clickedButtonAtIndex:(NSInteger)buttonIndex
{
    /* Handle action sheet here */
}

Action sheets support a type of button known as a destructive button. A destructive button is presented in an action sheet to denote a permanently destructive action, such as deleting a message or clearing a log. If you specify a destructive button, it is highlighted in red to alert the user to its dangerous nature:

mySheet.destructiveButtonIndex = 1;

Like navigation bars and toolbars, action sheets can support one of three different styles.

Style Description
UIActionSheetStyleDefault Default style: gray gradient background with white text
UIActionSheetStyleBlackTranslucent Transparent black background with white text
UIActionSheetStyleBlackOpaque Solid black background with white text

You can set the style by assigning a value to the action sheet's actionSheetStyle property:

mySheet.actionSheetStyle = UIActionSheetStyleDefault;

One of three methods may be used to display the action sheet. If you will be presenting the action sheet from within a view, use the showInView method to slide the sheet up from the bottom of the view:

[ mySheet showInView: self ];

To align an action sheet with the edge of a toolbar or tab bar, use the showFromToolBar or showFromTabBar methods:

[ mySheet showFromToolBar: toolbar ];
[ mySheet showFromTabBar: tabbar ];

3.7.3. Dismissing an Action Sheet

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:

[ mySheet dismissWithClickedButtonIndex: 1 animated: YES ];

3.7.4. EndWorld: Ending the World (with Confirmation)

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 the chief programmers conveniently wrote EndWorld.app, with a button 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 when he thought he was just social networking. In this example, assume that you've been contracted to add a confirmation alert to the EndWorld application—just in case the President didn't really mean to end the world (see Figure 3-3).

Figure 3-3. EndWorld example


The EndWorld application displays a navigation bar with an End World button and a convenient text window for typing in any last words. When the President presses the End World button, the application will first confirm by using an alert view.

You can compile this application, shown in Examples Example 3-27 through Example 3-31, with the SDK by creating a navigation-based application project named EndWorld. Be sure to pull out the Interface Builder code if you'd like to see how these objects are created from scratch.

Example 3-27. EndWorld application delegate prototypes (EndWorldAppDelegate.h)
#import <UIKit/UIKit.h>
#import "RootViewController.h"

@interface EndWorldAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    RootViewController *viewController;
    UINavigationController *navigationController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet RootViewController *viewController;
@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;

@end

                                          

Example 3-28. EndWorld application delegate (EndWorldAppDelegate.m)
#import "EndWorldAppDelegate.h"
#import "RootViewController.h"


@implementation EndWorldAppDelegate

@synthesize window;
@synthesize viewController;
@synthesize navigationController;

- (void)applicationDidFinishLaunching:(UIApplication *)application {

    window = [ [ UIWindow alloc ] initWithFrame:
        [ [ UIScreen mainScreen ] bounds ] ];
    viewController = [ [ RootViewController alloc ] init ];
    navigationController = [ [ UINavigationController alloc ]
        initWithRootViewController: viewController ];

    [ window addSubview: [ navigationController view ] ];
    [ window makeKeyAndVisible ];
}

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

@end

                                          

Example 3-29. EndWorld view controller prototype (RootViewController.h)
#import <UIKit/UIKit.h>
#import <UIKit/UITextView.h>

@interface RootViewController : UIViewController {
    UITextView *textView;
    UIBarButtonItem *endWorld;
    UIAlertView *endWorldAlert;
}
- (id)init;
- (void) endWorld;

@end

Example 3-30. EndWorld view controller (RootViewController.m)
#import "RootViewController.h"
#import "EndWorldAppDelegate.h"

@implementation RootViewController

- (id)init {

    self = [ super init ];
    if (self != nil) {

        /* Initialize Navigation Buttons */
        endWorld = [ [ [ UIBarButtonItem alloc ]
                  initWithTitle:@"End World"
                  style: UIBarButtonItemStyleDone
                  target: self
                  action:@selector(endWorld) ]
                autorelease ];
        self.navigationItem.rightBarButtonItem = endWorld;
    }

    return self;
}

- (void)loadView {
    CGRect bounds = [ [ UIScreen mainScreen ] applicationFrame ];
    [ super loadView ];

    textView = [ [ UITextView alloc ] initWithFrame: bounds ];
    textView.editable = YES;
    textView.text = @"Enter any last words here, then press End World.";

    self.view = textView;
}

- (void)endWorld {
    endWorldAlert = [ [ UIAlertView alloc ]
        initWithTitle:@"End The World"
        message:@"Warning: You are about to end the world."
        delegate:self
        cancelButtonTitle: @"My Bad"
        otherButtonTitles: @"OK", nil];

    endWorldAlert.delegate = self;
    [ endWorldAlert show ];

}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (alertView == endWorldAlert) {
        NSLog(@"Button %d pressed", buttonIndex);

        UIAlertView *myAlert = [ [ UIAlertView alloc ]
            initWithTitle:@"End The World"
            message: nil
            delegate:self
            cancelButtonTitle: nil
            otherButtonTitles: @"OK", nil ];

        if (buttonIndex == 0) {
            myAlert.message = @"Be more careful next time!";
        } else if (buttonIndex == 1) {
            myAlert.message = @"You must be connected to a WiFi network
 to end the world.";
        } else {
            myAlert.message = @"Invalid Button.";
        }

        [ myAlert show ];
    }

    [ alertView release ];
}


- (void)dealloc {
    [ textView dealloc ];
    [ endWorld dealloc ];
    [ super dealloc ];
}

@end

                                          

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

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

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

                                          

3.7.5. What's Going On

The EndWorld example illustrates the flow of control when using a delegate:

  1. The application instantiates as all other examples thus far have and invokes the program's main function, which invokes the EndWOrldAppDelegate class's applicationDidFinishLaunching method, and builds the appropriate window, view controller, and navigation controller classes.

  2. The view controller's init method is overridden to add a navigation bar button named End World. When tapped, the button's selector, endWorld, is notified.

  3. The selector creates a new UIAlertView object and presents it to the user. When the user clicks one of the buttons, the alert's delegate (currently the view controller) is notified by invoking its clickedButtonAtIndex method.

  4. The clickedButtonAtIndex method determines which button was tapped and constructs a new UIAlertView to inform the user of the result of his selection. Oops, not only do you need to be on a WiFi network to use the iTunes store, but also to end the world. Who knew?

3.7.6. Further Study

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

  • Convert the action view in this example to an action sheet. Experiment with adding different buttons. How many buttons will fit on the screen? How much text can they hold? How does the sheet resize itself to accommodate your design?

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

  • Check out the following prototypes in your SDK's header files: UIAlert.h and UITextField.h. You'll find these under /Developer/Platforms/iPhoneOS.platform, inside the UI Kit framework's Headers directory.