Sometimes
users need to be informed of changes when an application is running.
More than just a change in the current view is required when an internal
error event occurs (such as low-memory condition or a dropped network
connection), for example, or upon completion of a long-running activity.
Enter the UIAlertView class.
The UIAlertView class creates a simple modal alert window that presents a user with a message and a few option buttons (see Figure 1).
What Does Modal Mean?
Modal
UI elements require the user to interact with them (usually, to push a
button) before the user can do anything else. They are typically layered
on top of other windows and block all other interface actions while
visible.
Displaying a Simple Alert
In the preceding section, you
created a simple project (GettingAttention) with several buttons that
we’ll use to activate the different notification events. The first
button, Alert Me!, should be connected to a method stub called doAlert in GettingAttentionViewController.m. In this first exercise, we write an implementation of doAlert that displays an alert message with a single button that the user can push to dismiss the dialog.
Edit GettingAttentionViewController.m and enter the code shown in Listing 1 for doAlert.
Listing 1.
1: -(IBAction)doAlert:(id)sender { 2: UIAlertView *alertDialog; 3: alertDialog = [[UIAlertView alloc] 4: initWithTitle: @"Alert Button Selected" 5: message:@"I need your attention NOW!" 6: delegate: nil 7: cancelButtonTitle: @"Ok" 8: otherButtonTitles: nil]; 9: [alertDialog show]; 10: [alertDialog release]; 11: }
|
In lines 2 and 3, we declare and instantiate our instance of UIAlertView with a variable called alertDialog.
As you can see, the convenient initialization method of the alert view
does almost all the work for us. Let’s review the parameters:
initWithTitle: Initializes the view and sets the title that will appear at the top of the alert dialog box.
message: Sets the string that will appear in the content area of the dialog box.
delegate:
Contains the object that will serve as the delegate to the alert.
Initially, we don’t need any actions to be performed after the user
dismisses the alert, so we can set this to nil.
cancelButtonTitle: Sets the string shown in the default button for the alert.
otherButtonTitles: Adds an additional button to the alert. We’re starting with a single-button alert, so this is set to nil.
After alertDialog has been initialized, the next step is to show it by using (surprise!) the show method, as shown in line 9. Finally, as soon as we’re done with the alert, we can release it, as seen in line 10.
By the Way
If you prefer to set the alert message and buttons independent of the initialization, the UIAlertView class includes properties for setting the text labels (message, title) individually and methods for adding buttons (addButtonWithTitle).
Figure 2 shows the outcome of these settings.
By the Way
An alert doesn’t have to be a
single-use object. If you’re going to be using an alert repeatedly,
create an instance when your view is loaded and show it as needed—but
remember to release the object when you’re finished using it!
Creating Multi-Option Alerts
An
alert with a single button is easy to implement because there is no
additional logic to program. The user taps the button, the alert is
dismissed, and execution continues as normal. If you need to add
additional buttons, however, your application needs to be able to
identify the button pressed and react appropriately.
In addition to the
single-button alert that you just created, there are two additional
configurations to learn. The difference between them is how many buttons
you’re asking the alert to display. A two-button alert places buttons
side by side. When more than two buttons are added, the buttons are
stacked, as you’ll soon see.
Adding Buttons
Creating an alert with multiple buttons is simple: We just take advantage of the otherButtonTitles parameter of the initialization convenience method. Instead of setting to nil, provide a list of strings terminated by nil
that should be used as the additional button names. The cancel button
will always be displayed on the left in a two-button scenario or at the
bottom of a longer button list.
Did you Know?
At most, an alert view can
display five buttons (including the button designated as the “cancel”
button) simultaneously. Attempting to add more may result in some very
unusual onscreen effects, such as display of clipped/partial buttons.
For example, to expand the previous example to include two new buttons, the initialization can be changed as follows:
alertDialog = [[UIAlertView alloc]
initWithTitle: @"Alert Button Selected"
message:@"I need your attention NOW!"
delegate: nil
cancelButtonTitle: @"Ok"
otherButtonTitles: @"Maybe Later", @"Never", nil];
Write an updated version of the doAlert method within the doMultiButtonAlert method stub created earlier. Listing 2 shows the final code.
Listing 2.
-(IBAction)doMultiButtonAlert:(id)sender { UIAlertView *alertDialog;
alertDialog = [[UIAlertView alloc] initWithTitle: @"Alert Button Selected" message:@"I need your attention NOW!" delegate: nil cancelButtonTitle: @"Ok" otherButtonTitles: @"Maybe Later", @"Never", nil];
[alertDialog show]; [alertDialog release]; }
|
Pressing the Alert with Buttons! button should now open the alert view displayed in Figure 3.
Try pushing one of the
buttons. The alert view is dismissed. Push another? The same thing
happens. All the buttons do exactly the same thing—absolutely nothing.
Although this behavior was fine with a single button, it’s not going to
be very useful with our current configuration.
Responding to a Button Press with the Alert View Delegate Protocol
When I first started
using Objective-C, I found the terminology painful. It seemed that no
matter how easy a concept was to understand, it was surrounded with
language that made it appear harder than it was. A protocol, in my
opinion, is one of these things.
Protocols define a collection of methods that perform a task. To provide advanced functionality, some classes, such as UIAlertView,
require you to implement methods defined in a related protocol. Some
methods are required and others are optional; it just depends on the
features you need.
To
make the full use of an alert view, an additional protocol method must
added to one of our classes. We’ll be using our main application’s view
controller class for this purpose, but in larger projects it may be a
completely separate class. The choice is entirely up to you. A class
that implements a protocol is said to “conform” to that protocol.
To identify the button that was pressed in a multi-option alert, for example, our GettingAttentionViewController should conform to the UIAlertViewDelegate protocol and implement the alertView:clickedButtonAtIndex: method.
Edit the
GettingAttentionViewController.h interface file to declare that the
class will be conforming to the necessary protocol by modifying the @interface line as follows:
@interface GettingAttentionViewController : UIViewController
<UIAlertViewDelegate> {
Next, update the initialization code of the alert view in doMultiButtonAlert so that the delegate is pointed to the object that implements the UIAlertViewDelegate. Because this is the same object (the view controller) that is creating the alert, we can just use self:
alertDialog = [[UIAlertView alloc]
initWithTitle: @"Alert Button Selected"
message:@"I need your attention NOW!"
delegate: self
cancelButtonTitle: @"Ok"
otherButtonTitles: @"Maybe Later", @"Never", nil];
The alertView:clickedButtonAtIndex
method that we write next will receive the index of the button that was
pushed and give us the opportunity to act on it. To make this easier,
we can take advantage of the UIAlertView instance method buttonTitleAtIndex.
This method will return the string title of a button from its index,
eliminating the need to keep track of which index value corresponds to
which button.
Add the code in Listing 3 to GettingAttentionViewController.m to display a message when a button is pressed.
Listing 3.
1: - (void)alertView:(UIAlertView *)alertView 2: clickedButtonAtIndex:(NSInteger)buttonIndex { 3: NSString *buttonTitle=[alertView buttonTitleAtIndex:buttonIndex]; 4: if ([buttonTitle isEqualToString:@"Maybe Later"]) { 5: userOutput.text=@"Clicked 'Maybe Later'"; 6: } else if ([buttonTitle isEqualToString:@"Never"]) { 7: userOutput.text=@"Clicked 'Never'"; 8: } else { 9: userOutput.text=@"Clicked 'Ok'"; 10: } 11: }
|
To start, in line 3, buttonTitle is set to the title of the button that was clicked. Lines 4 through 10 test the value of buttonTitle against the names of the buttons that we initialized when creating the alert view. If a match is found, the userOutput label in the view is updated to something appropriate.
This is just one way to
implement the button handler for your alert. In some cases (such as
dynamically generated button labels), it may be more appropriate to work
directly with the button index values. You may also want to consider
defining constants for button labels.
Watch Out!
Don’t assume that
application processing stops when the alert window is on the screen!
Your code will continue to execute after you show the alert. You may
even want to take advantage of this by using the UIAlertView instance method dismissWithClickedButtonIndex: to remove the alert from the screen if the user does not respond within a certain length of time.
Adding Fields to Alerts
Although buttons can be used to
generate user input from an alert, you might have noticed that some
applications actually present text fields within an alert box. The App
Store, for example, prompts for your iTunes password before it starts
downloading a new app.
To add fields to your alert dialogs, you need to be a bit “sneaky.”
There isn’t a simple “add text field” option, but you can take advantage
of the method addSubView to add one view to another. This method is common to all subclasses of an UIView,
which includes the alert views we need to modify and the text field we
need to display. In short, we will manually create a field and position
it within the alert view. Because the alert view doesn’t “know” it’s
going to be there, we can use the alert view’s message text to create
space for the field. Sound bizarre? It is, but it isn’t difficult.
Let’s start by providing a place to store and reference the field.
Adding the Text Field Instance Variable
We don’t have Interface
Builder around to drag a field into an alert view, so we need to
declare, allocate, and initialize it manually. Open the
GettingAttentionViewController.h file and modify it to include a UITextField named userInput. Be sure to include a @property directive for it, too, as shown in Listing 4
Listing 4.
#import <UIKit/UIKit.h>
@interface GettingAttentionViewController : UIViewController <UIAlertViewDelegate> { IBOutlet UILabel *userOutput; UITextField *userInput; }
@property (retain, nonatomic) IBOutlet UILabel *userOutput; @property (retain, nonatomic) UITextField *userInput;
- (IBAction)doAlert:(id)sender; - (IBAction)doMultiButtonAlert:(id)sender; - (IBAction)doAlertInput:(id)sender; - (IBAction)doActionSheet:(id)sender; - (IBAction)doSound:(id)sender; - (IBAction)doAlertSound:(id)sender; - (IBAction)doVibration:(id)sender;
@end
|
Next, open the implementation file (GettingAttentionViewController.m) and add a @synthesize line for userInput immediately following the userOutput @synthesize line:
Finally, update the dealloc method to release userInput when the application is completed:
- (void)dealloc {
[userInput release];
[userOutput release];
[super dealloc];
}
Now we’re ready to build the doAlertInput method.
Adding a Text Field Subview
The steps that we take to add a
field to an alert may seem unusually convoluted, but, broken down,
they’re easy to understand. First, we initialize the alert, making sure
that it includes enough space in the view (by adding a message) so that
we can add a field over top of it. Then, we allocate and initialize a
new text field (userInput) and set its
color to white so that we can see it on top of the alert view. Finally,
we add the text field to the alert using the addSubView method to position it over top of where the alert’s message line would appear.
Enter the doAlertInput method in GettingAttentionViewController.m using the code in Listing 5.
Listing 5.
1: -(IBAction)doAlertInput:(id)sender { 2: UIAlertView *alertDialog; 3: 4: alertDialog = [[UIAlertView alloc] 5: initWithTitle: @"Please Enter Your Email Address!" 6: message:@"You won't see me" 7: delegate: self 8: cancelButtonTitle: @"Ok" 9: otherButtonTitles: nil]; 10: 11: userInput=[[UITextField alloc] initWithFrame: 12: CGRectMake(12.0, 70.0, 260.0, 25.0)]; 13: 14: [userInput setBackgroundColor:[UIColor whiteColor]]; 15: 16: [alertDialog addSubview:userInput]; 17: [alertDialog show]; 18: [alertDialog release]; 19: }
|
The beginning and end of this method should look familiar. It’s identical to doAlert, with the exception of the delegate being set to self (more on that a little later). The differences are in lines 11–16.
Lines 11–12 allocate, initialize, and assign a new UITextField instance to the userInput field. The initWithFrame method initializes the object with a rectangle returned by the CGRectMake() function. The values of 12.0, 70.0, 260.0, and 25.0 indicate
that the field will be located 12.0 points from the left side of the
view it is placed within and 70.0 points from the top. It will be 260.0
points wide and 25.0 points tall. Where did these values come from?
Experimentation! These are the values that will correctly position the
field over top of the message “You won’t see me.”
Line 14 sets the background of the text field to white.
Line 16 adds the field to the alert view using the addSubView method.
You should now be able to
Build and Run the GettingAttention application, click the I Need Input!
button, and see an alert with an input field, as demonstrated in Figure 4.
All that remains is being able to do something with the contents of the field—and that part is easy!
Accessing the Text Field
To access the input the user provided in the alert view, we just need to read the text property of userInput. Where do we do this? In the alert view’s delegate method alertView:clickedButtonAtIndex.
Ah ha! You say, “But didn’t we already use that method to handle the alert view from doMultiButtonAlert?”
Yes, we did, but if we’re clever, we can tell the difference between
which alert is calling that method and react appropriately.
Because we have access to the view object itself within the alertView:clickedButtonAtIndex
method, why don’t we just check the title of the view and, if it is
equal to the title of our input alert (Please Enter Your Email
Address!), we can set userOutput to the text the user entered in userInput. This is easily accomplished by a simple string comparison using the title property of the alert view object passed to alertView:clickedButtonAtIndex.
Add the following code snippet to the end of the alertView:clickedButtonAtIndex method:
if ([alertView.title
isEqualToString: @"Please Enter Your Email Address!"]) {
userOutput.text=userInput.text;
}
Build and run the
application with these changes in place. Now, when the alert view with
the text field is dismissed, the delegate method is called and the user
output label is properly set to the text the user entered.
Using these techniques, you
can expand the capabilities of alert views beyond the simple
implementation provided in the base iOS SDK.