UIAlertController as a Replacement for Single Column UIPickerViews displayed in UIActionSheets

Apple deprecated UIActionSheet in iOS 8 and now requires the use of UIAlertController instead. UIAlertController adds some really great new features and I think it is a good step forward but it does break backwards compatibility with some older apps in certain cases. One specific case, that I'm going to address in this post, is the presentation of a UIPickerView in a UIActionSheet.

Displaying a UIPickerView in a UIActionSheet was a quick way to allow the user to select one item from a list of multiple items. This was an especially helpful approach because it allowed for the display of UIPickerViews in the same fashion as the iOS keyboard. Users didn't have to learn a new way to interact with UIPickerViews.

Now that UIActionSheet is deprecated there are a number of approaches available to recreating this interaction.

  1. You could add the UIPickerView to your screen and modify AutoLayout constraint constants to slide the view on and off screen.
  2. If you have the ability to target only iOS 8 and above you could build your own reusable UIViewController and display it modally with the presentation style UIModalPresentationOverFullScreen.
  3. Or, if you only need to display a single column of data for the user to select from you can use the following UIAlertController approach that is quick and easy to implement.

UIAlertController can only display buttons and text fields, but it can display a lot of them! We can use this to our advantage.

The first thing to do is setup an Array that will act as the labels for the buttons in our UIAlertController.:

NSArray *items = @[@"Unfiltered",@"2015",@"2014",@"2013",@"2012"];
self.data = items;

The default behavior of UIAlertController requires that we attach a block to each button that will be executed when it is clicked. We will use this feature to make this work more like a UIPickerView. To do this we want to be able to reference these buttons by index once they are selected in the UIAlertController. UIPickerView has a method called selectedRowInComponent: so let's emulate that. Since we only will have a single column of buttons (that's all UIAlertController supports) we can ignore the component piece. The following example will set the text of a label based on the index of the button pressed.:

-(void)didSelectRowInAlertController:(NSInteger)row {
  self.outputLabel.text = [self.data objectAtIndex:row]; 
}`

Since blocks capture their enclosing scope we can use a counter variable to represent the index of each item in our list of button titles. To do this we can loop through our list of button titles, increment our counter variable by 1 each time, and then attach a block of code to each button that calls our didSelectRowInAlertController: method and passes in our counter variable as a parameter. Here's how this piece looks. I've included the entire viewDidLoad method for clarity. You can find this full project and code on Github.:

-(void)viewDidLoad {
   [super viewDidLoad];
   NSArray *items = @[@"Unfiltered",@"2015",@"2014",@"2013",@"2012"];
   self.data = items;

   int i = 0;

   UIAlertController *alert = [UIAlertController 
     alertControllerWithTitle:@"Filter Data"
                      message:@""
               preferredStyle:UIAlertControllerStyleActionSheet];

   for (NSString *item in items) {
       UIAlertAction* defaultAction = 
       [UIAlertAction actionWithTitle:item
                                style:UIAlertActionStyleDefault
                              handler:^(UIAlertAction * action) {
                                       [self didSelectRowInAlertController:i];
                                      }];

       [alert addAction:defaultAction];
       i++;
   }
   self.filterView = alert;
}

What's nice about this approach is that it is quick, the code is simple, and it is a reusable solution. I also really like that this works like the older UIPickerView approach which may make this a solution that more easily integrates with legacy code bases that require updating.

The biggest drawback to this approach is that you have to use Apple's default UI for the AlertController but I don't see that as a big deal. This is a great time saver so you can get your app shipped.

Here's what it looks like in the simulator.

filter240wide.gif