UIPickerView Tutorial — part 2

Customizing row height and component appearance

Before we continue the second installment in the series of basic tutorials about UIPickerView usage and customization, a short digression.

Most Cocoa/ Cocoa Touch tutorials assume that all connections to delegates, data sources and outlets are done visually in XCode. Our previous example was no exception.

You can see from the project (gave the link below) that both the delegate and the data source are assigned to the view controller. Referencing the UIPickerView outlet is also done graphically, in connection inspector.

There is another way to do it, programatically. I always encourage beginners to search programmatic ways to achieve same things they can do with a visual editor. There is a always a “plus” in learning what’s beneath the hood and, keeping all logic within MVC paradigm, creates a sound basis for your understanding of programming on this platform. If you remove connections in connection inspector, you have to declare delegates in the view controller, programmatically:

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.

    [_pickerView setDataSource:self];
    [_pickerView setDelegate:self];

Of course, _pickerView is our ivar auto-synthesized based on property declaration in the header (ViewController.h):

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UIPickerViewDataSource, UIPickerViewDelegate>

@property (strong, nonatomic) IBOutlet UIPickerView *pickerView;

More customization via delegate methods

As discussed, there are only two mandatory delegate methods that need to be overridden, both belong to UIPickerViewDataSource protocol class; these methods are:

@protocol UIPickerViewDataSource
@required

// returns the number of 'columns' to display.
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView;

// returns the # of rows in each component..
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component;
@end

There are six more optional delegate methods that are used for behavior and aspect customization of the UIPickerView. Today we will use only three of them.

@protocol UIPickerViewDelegate
@optional

// returns width of column and height of row for each component.
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component;
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component;

// these methods return either a plain NSString, a NSAttributedString, or a view (e.g UILabel) to display the row for the component.
// for the view versions, we cache any hidden and thus unused views and pass them back for reuse.
// If you return back a different object, the old one will be released. the view will be centered in the row rect
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component;
- (NSAttributedString *)pickerView:(UIPickerView *)pickerView attributedTitleForRow:(NSInteger)row forComponent:(NSInteger)component NS_AVAILABLE_IOS(6_0); // attributed title is favored if both methods are implemented
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view;

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component;

@end

pickerView widthForComponent:

This defines the width of each component (column or section) of the UIPickerView.

Let’s assume our application has a picker view with two components:

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
    return 2;
}

Then, we might want to create a component with a width of 140pt and another of 180pt (the width of device container is 320pt):

- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component
{
    return component == 0 ? 140.0f : (component == 1 ? 180.0f : 0.0f);
}

Please note that first element of components array is 0, not 1 !

pickerView viewForRow:

This allow customization of the view inside each picker view component and row. We might want to load custom labels, with 10pt padding to the left (constrained by each component width). Thus, first label will be contained within a view of 140x60pt; the second label will be contained within a view of 180x60pt with 10pt padding to the left. Due to the padding, we have to subtract padding values from actual width of each label:

- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view
{
    UILabel *label;
    if (component == 0) {
        label = [[UILabel alloc] initWithFrame:CGRectMake(10.0f, 0.0f, 130.0f, 60.0f)]; //x and width are mutually correlated
        label.textAlignment = NSTextAlignmentLeft;
        label.text = [_firstList objectAtIndex:row];

    } else {
        label = [[UILabel alloc] initWithFrame:CGRectMake(150.0f, 0.0f, 170.0f, 60.0f)]; //x and width are mutually correlated
        label.textAlignment = NSTextAlignmentLeft;
        label.text = [_secondList objectAtIndex:row];
    }

    return label;
}

pickerView rowHeightForComponent:

The last delegate method used in this tutorial defines the height of the rows. To keep it simple, we will define an overall height of 60pt for both components:

- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component
{
    return 60.0f;
}

Following this basic customization, running the app in simulator will look like this:

Simple and custom UIPickerView (two columns)

UIPickerView example, with two components (columns), an overall row height of 60pt and custom labels inside each row.

That’s it for today. You can grab the code here.

Leave a Reply

Your email address will not be published. Required fields are marked *