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", @"Dogs", @"Elephants", @"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:
#import <UIKit/UIKit.h> @interface ViewController : UIViewController <UIPickerViewDataSource, UIPickerViewDelegate> @property (strong, nonatomic) IBOutlet UIPickerView *pickerView; @property (strong, nonatomic) NSMutableDictionary *myDataSource; @property (strong, nonatomic) NSMutableArray *firstList; @end
Go and right click on UIPickerViewDataSource
and choose “Jump to definition” from the popup menu. You will be directed to UIPickerViewDataSource
protocol.
See lines 81–89, there are two methods declared which are required, thus is mandatory to implemented both in the controller:
@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
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 { return 1; }
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.
Customization
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:
@protocol UIPickerViewDelegate<NSObject> @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
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]; return label; }
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 UIPickerView
.
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.
How to Dismiss PickerView with Done button on UIToolBar? Unable to fire click event.please help.
Hi, Niha, Thank you for your comment.
I am not quite sure I understand your question. Could you elaborate a bit on your needs ?
Will probably make the subject of a new post in this series.
Regards,
AP
I’ve created a custom UIPickerView.
I’ve added a UIToolbar with a UIBarButtonItem for the ‘done’ button on it.
Now i want to hide picker and tool bar both on button(done button) click.
It works in ios6 but in ios 7 it doesn’t.
Hi, Niha. Thank you for these clarifications. It’s nice to see comments. Make me think my efforts are not in vain. Will post an update to this PickerView tutorial, covering this topic. Stay tuned.
Regards,
AP
Hi, AP!
Thanks for your reply and i will stay tuned.
Regards,
NIHA
RE:
“Well, I am not an expert in iOS but immediately I suspected …”
You do a very good job of faking it!
Great site.
Thank you for your comment. But, really, I am not an expert. :)))
Stay tuned; looking forward for any comments or tutorial requests.
Regards,
AP