b2cloud

3rd October 2013

Dynamic UIAlertViews and UIActionSheets (and best practices)

Guides | Tutorial By 4 years ago

Too often I see UIAlertViews and UIActionSheets being setup poorly. When adding more than 2 buttons, I see a lot of people in the delegate method hardcoding button indexes (if you find yourself hardcoding anything, often you should look for a better solution).

Something like the following:

- (void) presentOptions
{
	[[[UIAlertView alloc] initWithTitle:@"Choose wisely"
					message:@"Really delete all of your important information?"
					 delegate:self
			  cancelButtonTitle:@"Cancel"
			 otherButtonTitles:@"Delete everything", @"Do something else", nil] show];
}

- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex
{
	if(buttonIndex == 0)
	{
		NSLog(@"Cancel");
	}
	else if(buttonIndex == 1)
	{
		NSLog(@"Delete everything");
	}
	else if(buttonIndex == 2)
	{
		NSLog(@"Do something else");
	}
}

Now these button indexes would have worked during testing, but in one of the iOS7 betas the alert buttons all changed positions, and a lot of programs would be disobeying the user’s intentions when button at index 0 became button at index 2… with potentially disastrous results. There is a better way.

When using two buttons, cancel and confirm, in the delegate use the following code (this compares the button tapped to the cancel button, if it’s only two buttons and it’s not the cancel button, then the user has confirmed it):

- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex
{
	if(buttonIndex != alertView.cancelButtonIndex)
	{
		//	Do stuff
	}
}

If you have more than two buttons, or are adding buttons dynamically then this obviously wont work. Instead I recommend creating the alert with no buttons, adding the buttons one by one and then showing the alert. When you add a button to the alert you get back the index of the button, which can be saved and then compared when you get the delegate callback.

First declare the indexes somewhere:

NSInteger deleteEverythingButtonIndex;
NSInteger doSomethingElseButtonIndex;

Then create and setup your alert, saving the button indexes for later use:

UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Choose wisely"
						message:@"Really delete all of your important information?"
						   delegate:self
					 cancelButtonTitle:nil
					  otherButtonTitles:nil];

deleteEverythingButtonIndex = [alert addButtonWithTitle:@"Delete everything"];
doSomethingElseButtonIndex = [alert addButtonWithTitle:@"Do something else"];
[alert setCancelButtonIndex:[alert addButtonWithTitle:@"Cancel"]];

[alert show];

And compare them dynamically in the delegate callback:

- (void)alertView:(UIAlertView *)alertView willDismissWithButtonIndex:(NSInteger)buttonIndex
{
	if(buttonIndex == deleteEverythingButtonIndex)
	{
		NSLog(@"Delete everything");
	}
	else if(buttonIndex == doSomethingElseButtonIndex)
	{
		NSLog(@"Do something else");
	}
}

Easy.

The same is applied to the UIActionSheet, with the addition of the destructiveButtonIndex property.

  • mattyInNc

    I hate the delegation pattern with UIAlertView and UIActionSheet, so I’ve written a categories that include delegate wrapper objects in order to allow me to use postDisplay and postDismissal blocks. Using those, it’s trivial to query the title of the button that was pressed and compare it against the list I created 2 lines above. You’ve got to compare something to something, and beyond Yes/No or OK/Cancel pairs, this seems just as good a way as any for me. Here’s a typical scenario:

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@”Desired Action?” message:@”What do you want to do with this photo?” delegate:nil cancelButtonTitle:@”Cancel” otherButtonTitles:@”Set As Cover Image”, @”Delete Photo”, nil];
    [alert showWithPostDisplayBlock:nil postDismissal:^(UIAlertView *alert, NSInteger buttonIndex) {
    NSString *title = [alert buttonTitleAtIndex:buttonIndex];
    if ([title isEqualToString:@”Delete Photo”]) {
    // Delete the photo

    } else if ([title isEqualToString:@”Set As Cover Image”]) {
    // Set the photo as this item’s cover image

    }
    }];

    • Tom

      Block alerts are nice, in iOS8 Apple has actually built a replacement for UIAlertView and UIActionSheet which uses blocks. Check out the UIAlertController.

      • mattyInNc

        Yeah, I’ve looked at them some. For now, all of my UIAlertView and UIActionSheet code still works in iOS 8. I like the new model (streamlining them into a single model), but it’s annoying to have to support multiple code bases. For now, I’m content with my current solution.

Recommended Posts

iOS view hit regions

Post by 4 years ago

According to the Apple Human Interface Guidelines, the smallest point size a control should be in iOS is 44×44 points. In some designs you will get really small controls which you need to still work

Connect With Us

We're trusted by some of the largest businesses and enterprises to build digital products that matter.