Oct 14 2010

UIPickerView with custom view

Rod Liberal

I’ve been finding that the UIPickerView is just not the friendliest of controls with which to work.
I needed a picker with two pieces of text on it from some core data objects and figured that would be something fairly simple to do. Not so.

The UIPickerView has two methods to load data into it:

  • pickerView:titleForRow:forComponent
  • pickerView:viewForRow:forComponent:reusingView

The first one is pretty simple; you return a string and that string will be used to display data for that row.

The second one allows you to return any views that inherent from UIView, so basically any control or custom views.

I decided I was going to need two UILabels inside a UIView in order to accomplish what I wanted.
There are several problems which arise from going this route, however.

First and the least of the problems, you will have to change the background color of each label to NSColorClear to display through your grid row.
Once your labels are added and you touch them to select one, a highlighted frame will show around them making for a very unpleasant UI experience.
Lastly, the labels will overtake the pickers touch response, so you’ll have to fix that, which is a nice segway for some code.

  1. Let’s start by creating subclasses of UILabel and UIView.
    From the File menu, select New File…/Objective-C Class (Under Cocoa Touch Class; select subclass of UIView) and check “Also create …h”
    Name your new UIView subclass something like CustomPickerView.
  2. Repeat the process and create another view called CustomPickerLabel
    Once this class is created, change its parent class from UIView to UILabel
    In the implementation code for both of your new classes, add the following exact same code:

  3. - (void)didMoveToSuperview
    {
    if ([[self superview] respondsToSelector:@selector(setShowSelection:)])
    {
    [[self superview] performSelector:@selector(setShowSelection:) withObject:NO];
    }
    }

  4. Create your controller where your UIPickerView will reside, assign UIPickerView delegates and datasources, and create the delegate and datasource methods required.
    (Remember though, instead of using pickerView:titleForRow, use pickerView:viewForRow instead)
  5. In your controller implementation, in the pickerView:viewForRow method, let’s create our custom view and labels:

    // create custom view to take the width of your pickerView
    CustomPickerView *customView = [[CustomPickerView alloc] initWithFrame:CGRectMake(0.0, 0.0, 290, 32)];

    // first label will be displayed on the left of view, taking 3/4 of the row
    CustomPickerLabel *pickerLabelLeft = [[CustomPickerLabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 190, 32)];

    [pickerLabelLeft setTextAlignment:UITextAlignmentLeft];
    [pickerLabelLeft setFont:[UIFont boldSystemFontOfSize:15]];
    pickerLabelLeft.backgroundColor = [UIColor clearColor]; // don't forget to do this to clear the label's color
    [pickerLabelLeft setText:@"Refrigerator"];

    // second label will be displayed on the right taking remaining width
    CustomPickerLabel *pickerLabelRight =[[CustomPickerLabel alloc]
    initWithFrame:CGRectMake(pickerLabelLeft.frame.size.width, 0.0, 100, 32)];

    [pickerLabelRight setTextAlignment:UITextAlignmentRight];
    [pickerLabelRight setFont:[UIFont boldSystemFontOfSize:15]];
    pickerLabelRight.backgroundColor = [UIColor clearColor];
    [pickerLabelRight setText:@"$4.99"];

    // THIS IS IMPORTANT
    // you MUST set the view's userInteractionEnabled property to NO to let the picker regain touch events
    customView.userInteractionEnabled = NO;

    //finally, add both labels to custom view and return view
    [customView addSubview:pickerLabelLeft];
    [customView addSubview:pickerLabelRight];

    return customView;

Don’t forget to set your numberOfComponentsInPickerView and numberOfRowsInComponent methods and you’re all set.
This will give you a customized and interactive UIPickerView.