Recently I was asked by a friend to provide some support with UIPickerView in iOS 7. He was a bit puzzled by what it seemed to be the impossibility to pad the content of each picker view row to a certain distance from the left side of the UIPickerView. Basically, let’s consider the following example. By default, a simple app with the following data source:
_firstList = (NSMutableArray *)@[@"Cats",
@"This example has just 20px on left"];
will give the following result:
I was told that, changing parameters in Attribute Inspector (cmd + opt + 4 shortcut in XCode) did not make any difference. Well, I am not an expert in iOS but immediately I suspected that my friend overlooked to implement one of UIPickerView delegate methods.
Basically — and I hope you understood that already — there is no way of customizing the look and feel of your iOS app without rolling your sleeves and getting dirty in coding UI properties. I.e., programatically define the way your UI should look like. Don’t get me wrong, doing things in XCode’s visual UI editor (former Interface Builder) is cool and, for the majority of simple applications, is quite sufficient. But simple is the keyword here. Once you want to go complex and adapt your UI depending on any constraints within the application logic, you have to do it programatically. With some exceptions on the bindings, maybe, but that’s another story.
However, taking the programmatic approach has the added benefit of improved support for debugging. And this is a huge plus. Go and take a look at Apple’s
UIPickerView class documentation and another look at
UIPickerViewDelegate Class Reference and one final to UIPickerViewDataSource Protocol Reference. Don’t be fooled by what seem to be a modest number of methods of these classes. Make no mistake: UIPickerView inherits from
UIView, thus all
UIView‘s configuration methods also apply to
UIPickerView. The amount of possible options provided by XCode’s Attribute Inspector is much less than what you can achieve programatically. And the massive customization that some application require, will implicitly require a programmatic approach.
Getting back to the simple
UIPickerView example, the problem here is that there are at least two delegate methods that should be overridden. Assuming you have the following view controller header:
@interface ViewController : UIViewController <UIPickerViewDataSource, UIPickerViewDelegate>
@property (strong, nonatomic) IBOutlet UIPickerView *pickerView;
@property (strong, nonatomic) NSMutableDictionary *myDataSource;
@property (strong, nonatomic) NSMutableArray *firstList;
Go and right click on
UIPickerViewDataSource and choose “Jump to definition” from the popup menu. You will be directed to
See lines 81–89, there are two methods declared which are required, thus is mandatory to implemented both in the controller:
// 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;
The first method define how many components (or “sections” or “columns”) the picker view has. It takes as argument only an object of
UIPickerView type and returns the number of components. This is important because the
UIPickerView cannot display any data if it does not know how many components to build, to hold that data. In our case is 1 as we have only one component (“section” or “column”):
– (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
The second method define how many rows are in each component (i.e. each
UIPickerView “columns”). It takes as argument an object of
UIPickerView type and another object as
NSInteger for the component ID; this is also important and mandatory. The picker view cannot display any data if it does not know how many rows to prepare to hold the data. Usually, this is returning a simple count of the objects in the data source, like that:
– (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
return [_firstList count];
Ok, that’s it. All which is required (assuming you have connected the delegate and data source items within your controller class to the UIPickerView outlet) is to override these two delegate methods and you will get the app as in the picture above.
However, things do not stop here. And we are getting now to the topic of today’s
UIPickerView musings. If you want to have a custom look and feel of your picker view content, you have to define custom views for all or each of picker view’s rows. And for this, the delegate has a special method
pickerView viewForRow:(NSInteger)row forComponent:
which you can see at line 104 of
UIPickerViewDelegate protocol class:
// 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;
I want to display, in each UIPickerView row, a label which is contained in a 300px wide view, padded to 20px on the left and left-aligned. For this, I am overriding the above delegate method:
– (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(20.0f, 0.0f, 300.0f, 60.0f)]; //x and width are mutually correlated
label.textAlignment = NSTextAlignmentLeft;
label.text = [_firstList objectAtIndex:row];
Please note line 40. I am creating a custom label, named *label, that will be used as custom view for each row. Another thing to note is the next line below, when I am aligning the text to the left. See that I used
NSTextAlignmentLeft method instead
UITextAlignmentLeft which is deprecated. If I build the app, I will get:
Which is exactly what I wanted: to have a custom view in each row, left–aligned and padded to 20px from left margin of the
Ok, that’s for today: a very simple UIPickerView example. Next, we will enhance a bit our custom picker and add some more fancy logic behind. Stay tuned.