How to make uipickerview with only 3 visible rows? Like this:
You have to add UIDatePicker Programmatically to display three rows. Adjust the picker size according to your need.
UIDatePicker *datePicker = [[UIDatePicker alloc] init];
datePicker.frame = CGRectMake(0, 160, 320, 160);
datePicker.datePickerMode=UIDatePickerModeDate;
[self.view addSubview:datePicker];
I noticed that most of the responses mentioned either UIDatePicker or the number of Components, neither of which seem right since the question specifically asks about UIPickerView and the number of Rows Visible.
I found a solution by implementing:
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component
{
return (80.0);
}
This allows you to make the height of the row larger (or smaller) so that fewer (or more) rows appear in the control, although the vertical size of the control is the same.
I do believe that the size of the control can be changed to avoid all the excess spacing, but I haven't addressed that issue yet.
Row Height 25.0
Row Height 80.0
In addition to #GRW's answer. So, you cant set arbitrary height to a picker view; but you Can: change row height to have 3 visible rows; AND apply affine transform to scale down the whole picker view.
To maintain the aspect ratio:
you must scale not only y, but x too with the same scale factor;
divide -pickerView:widthForComponent: by the scale factor;
divide -pickerView:rowHeightForComponent: by the scale factor;
if you have labels / text in components' rows, the font size must by divided by the factor as well.
// consider
CGFloat _scaleFactor = 0.5;
- (void)viewDidLoad
{
[super viewDidLoad];
self.pickerView.transform = CGAffineTransformMakeScale(_scaleFactor, _scaleFactor);
}
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component
{
return desiredHeight / _scaleFactor;
}
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component;
{
return desiredWidth / _scaleFactor;
}
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component
{
return desiredHeight / _scaleFactor;
}
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row
forComponent:(NSInteger)component reusingView:(UIView *)view
{
UILabel *label = (UILabel *)view;
if (!label) {
label = [[UILabel alloc] init];
CGFloat desiredFontSize;
label.font = [UIFont systemFontOfSize:(desiredFontSize / _scaleFactor)];
}
// set text from data source
return label;
}
The difference versus blocking out not-to-be-visible rows is quite subtle; however, in my opinion the scaled picker on the right looks way more natural... In fact, that makes a big difference.
(scale factor used is 0.5)
Try following code -
#pragma mark - picker datasource methods
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 1;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
return 3;
}
#pragma mark - picker delegate
-(NSString *) pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
return [YourArray objectAtIndex:row];//your array that contains data
}
Please try below code :
//Method to define how many columns/dials to show
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 3;
}
// Method to define the numberOfRows in a component using the array.
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent :(NSInteger)component
{
if (component==0)
{
return [arrFirst count];
}
else if (component==1)
{
return [arrSecond count];
}
else
{
return [arrThird count];
}
}
// Method to show the title of row for a component.
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
switch (component)
{
case 0:
return [arrFirst objectAtIndex:row];
break;
case 1:
return [arrSecond objectAtIndex:row];
break;
case 2:
return [arrThird objectAtIndex:row];
break;
}
return nil;
}
Thanks, Do let me know if you have any problems.
Related
Note: I have the delegate set properly and UIPicker works perfectly in ios6.
In ios5 , the call back for didSelectRow: is called only if i select row0 or row5 only . I have 12 rows .
Does anyone have clue what could be wrong?
NOte: The UIPicker's delegate and data source are pointing to the class having the below code.
// returns the number of 'columns' to display.
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
if ([pickerView isKindOfClass:[MIDatePicker class]]) {
switch (self.pickermode) {
case MIDatePickerModeDayMonth:
return 2;
break;
case MIDatePickerModeDayMonthYear:
return 3;
break;
default:
break;
}
}
return 3;
}
// returns the # of rows in each component..
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
if ([pickerView isKindOfClass:[MIDatePicker class]]) {
//month , day , year
switch (component) {
case 0:{
return [self.monthsArray count];
}
break;
case 1:
{
return [self.daysArray count];
}
case 2:{
return [self.yearArray count];
}
default:
break;
}
}
return 3;
}
// returns width of column and height of row for each component.
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component{
// month , day ,year
switch (component) {
case 0:{
return self.picker.frame.size.width/2;
}
case 1:
return self.picker.frame.size.width/6;
case 2:{
return self.picker.frame.size.width/3;
}
default:
break;
}
return self.picker.frame.size.width;
}
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component{
return pickerView.frame.size.height/5;
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{
// month , day ,year
switch (component) {
case 0:{
return [NSString stringWithFormat:#" %# ",[self.monthsArray objectAtIndex:row]];
}
case 1:
return [self.daysArray objectAtIndex:row];
case 2:{
return [self.yearArray objectAtIndex:row];
}
default:
break;
}
return #"";
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
NSLog(#"selected row:%d , component:%d" , row,component);
}
In ios5: Now i am getting all the calls for didSelectRow by casting float to integer.
My observations: The default picker height is 216.0 . The same piece of code works if i change the row height to pickerView.frame.size.height/3 or pickerView.frame.size.height/4 or even pickerView.frame.size.height/6; reason: 216.0 is perfectly divisible by 3,4,6 and when divided by 5 it has 0.2 fraction. Thats the reason i am getting the didSelect call only for row1 and row6 in case of pickerView.frame.size.height/5.
Decent Fix might be:
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component{
return (int) pickerView.frame.size.height/5;
}
I have a picker returning 3 components. I was told you can change the size of the views by doing the following, but I am not too sure how to implement this...
implementing the UIPickerViewDelegate method pickerView:widthForComponent: method to size the components (probably 150, 20, and 150, or so).
I see in the documentation you set up a method like this, but dont know what to do next?
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component
{
}
For using this you have to implement UIPickerViewDelegate...... and assign it to your pickerView.....
if this method is being written in the same class where you are creating pickerView......
then write.....
pickerView.delegate = self;
and in header implement this delegate...
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component
{
if(component == 0)
return 150.0;
else if(component == 1)
return 20.0;
else if(component == 2)
return 150.0;
else
return 150.0;
}
Components are numbered left-to-right.
Thanks,
Does any one know how to make dependent UIPickerView. For example when i select row 2 of component one the titles for component two change?
I have looked on the internet there is no real answer, i have tried using if and switch statements but they just crash.
It depends on how you are going to keep data. For an example if you have an array as the value of a key of a dictionary, and that dictionary have different such keys, the first column will be the keys, and on selecting one you will be displaying the array in the other column (component). - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView method should return 2.
In
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
method, you need to give the number of keys in dictionary for component 1, and count of the array of the currently selected key.
eg
if(component==0) return [[DICTIONARY allKeys] count];
else return [[DICTIONARY objectForKey:#"SELECTED_KEY"] count];
Then,
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
selectedIndex = [pickerView selectedRowInComponent:0];
if (component == 1 && !(selectedIndex < 0)) {
[pickerView reloadComponent:2];
[pickerView selectRow:0 inComponent:2 animated:YES];
}
}
and
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view {
UILabel *pickerRow = (view != nil) ? view : [[[UILabel alloc] initWithFrame:CGRectMake(5, 0, 115, 60)] autorelease];
pickerRow.font = [UIFont boldSystemFontOfSize:14];
pickerRow.textAlignment = UITextAlignmentLeft;
pickerRow.backgroundColor = [UIColor clearColor];
pickerRow.textColor = [UIColor blackColor];
pickerRow.numberOfLines = 0;
if (component == 0) {
pickerRow.text = #"DICTIONARY_ROW'th_KEY";
}
else {
pickerRow.text = [[dictionary objectForKey:#"SELECTED_KEY"] objectAtIndex:row];
}
return pickerRow;
}
I'm sure this can be accomplished using a combination of the delegate method pickerView:didSelectRow:inComponent: and reloadComponents.
Hope this helps, I had a similar situation, my requirement was when i select a country name in a component, the cities of the country should be loaded in the second component. So what i did was, i had 2 pickerview side by side and when the user selects a row in country picker i re-initiated the second picker with appropriate data.
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
if (pickerView == countryPickerView)
{
if(PickerViewCity)
{
[PickerViewCity removeFromSuperview];
[PickerViewCity release];
PickerViewCity=nil;
}
PickerViewCity = [[UIPickerView alloc] initWithFrame:CGRectMake(160, 38, 320, 240)];
PickerViewArrayCity=[[countryPickerViewArray objectAtIndex:[pickerView selectedRowInComponent:0]]objectForKey:#"nodeChildArray"];
//NSLog(#"%#",PickerViewArrayCity);
PickerViewCity.showsSelectionIndicator = YES; // note this is default to NO
PickerViewCity.delegate = self;
PickerViewCity.dataSource = self;
CGAffineTransform s20 = CGAffineTransformMakeScale(0.5, 0.58);
CGAffineTransform t21 = CGAffineTransformMakeTranslation(-80,-50);
PickerViewCity.transform = CGAffineTransformConcat(s20, t21);
PickerViewCity.layer.masksToBounds = YES;
[someview addSubview:PickerViewCity];
}
if (pickerView == PickerViewCity)
{
}
}
you need to set conditions for each row of the first component (with index 0) and open a switch to select each index, I did it and worked for me:
on ...titleForRow:(NSinteger)row forComponent(NSInteger)component
{
if (component == 0) // this is first component from left to right
return [arrayForFirstComponent objectAtIndex:row];
if (component == 1) // this is second component from left to right
switch([pickerIBOutletAsYouNamedIt selectedRowInComponent:0]) // here you set content of component two depending on which row is selected from component 1
{
case 0: // remember index starts at 0
return [arrayForComponent2ForRow1inComponent1 objectAtIndex:row];
break;
//... and so on for each row in the component 1 (with index 0)
}
}
remember: YOU HAVE TO DO THIS ALSO WITH
- ...numberOfRowsInComponent:(NSInteger)component
and with
- ...didSelectRow:(NSInteger)row inComponent:(NSInteger)component
and in his last one, in selection of row at component 0 call [pikcerIBOutlet reloadComponent:1]
I think, you like to change the corresponding item of one component when change the other. It's maybe a better solution.
Code:
- (void)pickerView:(UIPickerView *)pickerView
didSelectRow:(NSInteger)row
inComponent:(NSInteger)component {
if (component == 0) {
//Here First Part of the Pickerview is represented using 0.
// dependentPicker is the Pickerview outlet
[dependentPicker selectRow:row inComponent:1 animated:YES];
[dependentPicker reloadComponent:1];
}
else
{
[dependentPicker selectRow:row inComponent:0 animated:YES];
[dependentPicker reloadComponent:1];
}
}
Note: When you use this code the component values must be Hold by two NSArray and the order of the corresponding dependent values must be same in both arrays.
I am trying to implement a custom picker. It has 3 components. Now, I want to gray out some values in the second component based on the first component value selected. I have referred to many sites and tried googling out to disable the values.(To be precise, something like the uidatepicker where if we select feb, 29 and 30 will be grayed. But i am trying to do it in custom picker implementing my own contents). Could some one help me how to go about for disabling the values in custom picker? I tried using the function
[myPickerView selectRow:27 inComponent:1 animated:NO];
based on the if conditions also. It directly goes to the value, but doesn't gray out the unnecessary values.
My code:
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
NSString *returnStr = #"";
// note: custom picker doesn't care about titles, it uses custom views
if (pickerView == myPickerView)
{
if (component == 0)
{
returnStr = [pickerViewArray objectAtIndex:row];
}
else if(component ==1)
{
returnStr = [pickerViewArray1 objectAtIndex:row];
}
else
{
returnStr = [pickerViewArray2 objectAtIndex:row];
}
}
return returnStr;
}
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component
{
CGFloat componentWidth = 0.0;
if (component == 0)
{
componentWidth = 140.0;
}// first column size is wider to hold names
else if(component ==1)
{
componentWidth = 40.0;
}// second column is narrower to show numbers
else if(component == 2)
{
componentWidth = 100;
}
return componentWidth;
}
- (CGFloat)pickerView:(UIPickerView *)pickerView rowHeightForComponent:(NSInteger)component
{
return 40.0;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
//return [pickerViewArray count];
if (component == 0)
{
return [pickerViewArray count];
}// first column size is wider to hold names
else if(component ==1)
{
return [pickerViewArray1 count];
}
else
{
return [pickerViewArray2 count];
}
}
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 3;
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
if (pickerView == myPickerView) // don't show selection for the custom picker
{
// report the selection to the UI label
label.text = [NSString stringWithFormat:#"%# %# %#",
[pickerViewArray objectAtIndex:[pickerView selectedRowInComponent:0]],
[pickerViewArray1 objectAtIndex:[pickerView selectedRowInComponent:1]],[pickerViewArray2 objectAtIndex:[pickerView selectedRowInComponent:2]]];
}
}
I haven't tried it, but there is -pickerView:viewForRow:forComponent:reusingView:. Its documentation says:
If the previously used view (the view parameter) is adequate, return that. If you return a different view, the previously used view is released. The picker view centers the returned view in the rectangle for row.
so if I undertstand corectly, you could take that passed in view, change it (grey out) and return it.
Have you declared that your controller conforms to the protocol in your header file? Do you know if the delegate methods above are being called when you change you selection, particularly the didSelectRow one?
To find out just set a breakpoint on that method, build and run and try changing the pickerview selection.
Once you have that sussed, it is matter of analysing the selection and pulling the correct data source, then reloading the component, which you seem to be missing from the implementation above:
[pickerView reloadComponent:(int)];
I hope it helps.
Cheers,
Rog
What I would like to do is this:
A UIPickerView is shown. If the user touches the selected row, the row is locked (it is a multi-component picker) and the other components are free to spin. If the row has already been locked and the user touches the locked row, the row is then unlocked and free to spin. I have the locking part coded already using a button. I would like to remove the button and replace with the highlighted picker option.
I have tried:
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
}
Apparently this only fires if the row has not been selected already so when I touch a row that is in the highlighted region, this event does not fire.
I then tried
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"touchesBegan");
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"touchesMoved");
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"touchesEnded");
}
None of these events fire when the picker is touched.
Any ideas on how to detect when a highlighted/selected row in a picker is touched by the user?
Well -- there is a simple workaround that did exactly what I wanted to accomplish. Basically I wanted to have the user tap the selection bar on a multicomponent picker view and have that component locked while the others are free to spin.
Here is what I did:
First - turn off the option to show the selection bar.
Second - create three labels - one for each component -- the labels are the same height and location as the selector bar, but there is one over each component. They but each other to appear to be a solid bar.
Third - create a method to change the color of the label to indicate that it is locked to the user. I am also using a boolean flag to let the program processes know when a component is locked.
- (IBAction) lockButtonPress:(id)sender {
// determine which button was pressed....
int btnPressed = 0;
if (leftSelectionBar.touchInside) btnPressed = 1;
if (centerSelectionBar.touchInside) btnPressed = 2;
if (rightSelectionBar.touchInside) btnPressed = 3;
// we are not going to make this difficult -- images for different states..... default in viewWillShow
switch (btnPressed) {
case 1:
if (lockSelected0) {
lockSelected0 = FALSE;
[leftSelectionBar setBackgroundColor:[UIColor blueColor]];
[leftSelectionBar setAlpha:0.25];
} else {
lockSelected0 = TRUE;
[leftSelectionBar setBackgroundColor:[UIColor redColor]];
[leftSelectionBar setAlpha:0.45];
}
break;
case 2:
if (lockSelected1) {
lockSelected1 = FALSE;
[centerSelectionBar setBackgroundColor:[UIColor blueColor]];
[centerSelectionBar setAlpha:0.25];
} else {
lockSelected1 = TRUE;
[centerSelectionBar setBackgroundColor:[UIColor redColor]];
[centerSelectionBar setAlpha:0.45];
}
break;
case 3:
if (lockSelected2) {
lockSelected2 = FALSE;
[rightSelectionBar setBackgroundColor:[UIColor blueColor]];
[rightSelectionBar setAlpha:0.25];
} else {
lockSelected2 = TRUE;
[rightSelectionBar setBackgroundColor:[UIColor redColor]];
[rightSelectionBar setAlpha:0.45];
}
break;
default:
break;
}
}
And that's it.... simple ;)
(void)pickerView:(UIPickerView *)thePickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
//Custom code here
//For example, If you have an NSArray or NSMutableArray called as "list" whose values are shown on the UIPickerView - [list objectAtIndex:row] where row is index returned by the UIPickerView event will return the object itself.
}
The following code snippet will intercept tap gestures on a UIPickerView and determine if the tap was within the selection indicator of the UIPickerView:
First, we'll add a UITapGestureRecognizer to intercept tap gestures. Note, that we don't want to cancel the touches because the UIPickerView should still do it's thing spinning the wheel and all.
- (void)viewDidLoad
{
[super viewDidLoad];
UITapGestureRecognizer* gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(pickerViewTapGestureRecognized:)];
gestureRecognizer.cancelsTouchesInView = NO;
[self.pickerView addGestureRecognizer:gestureRecognizer];
}
Second, we'll check, if the tap was within the selection indicator of the UIPickerView (assuming that the selection indicator uses about 15% of the height of the UIPickerView — you may have to adjust this value):
- (void)pickerViewTapGestureRecognized:(UITapGestureRecognizer*)gestureRecognizer
{
CGPoint touchPoint = [gestureRecognizer locationInView:gestureRecognizer.view.superview];
CGRect frame = self.pickerView.frame;
CGRect selectorFrame = CGRectInset( frame, 0.0, self.pickerView.bounds.size.height * 0.85 / 2.0 );
if( CGRectContainsPoint( selectorFrame, touchPoint) )
{
NSLog( #"Selected Row: %i", [self.currentArticles objectAtIndex:[self.pickerView selectedRowInComponent:0]] );
}
}
You should NOT implement the
- (void)pickerView:(UIPickerView*)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
since we're detecting selection on our own now.