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.
Related
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.
I'm using iCarousel as a slot machine, for those who doesn't know iCarousel it is a UIScrollview with (paged, scrolling views). So in my app, I scroll it, then when it stops it shows the Image(Result) for 3 seconds, then a button pop-ups, if you press the button, it will delete the View(Image Result) then Rotates again.
Here is my way of deleting my carousel view:
NSInteger CurrentImage = carousel.currentItemIndex;
[carousel removeItemAtIndex:CurrentImage animated:YES];
[images removeObjectAtIndex:CurrentImage];
But then when the carousel.numberOfItems has 1 item left, it doesnt scroll, instead its just stuck there.
So I made a way to make it scroll even it has only 1 Item(index) left.
I inserted it with the last index, so I tried this:
[self.carousel insertItemAtIndex:0 animated:NO];
(but doesnt work)
then I tried another:
int lastObject = [images objectAtIndex: ([images count]-1)]
[self.carousel insertItemAtIndex:lastObject animated:NO];
(still doesnt work)
and also this:
int count = [images count];
[images objectAtIndex:count - 1];
(still no luck)
Am I doing wrong? or What are other ways?
Or can I just duplicate the last View? Thanks for the help.
EDIT:
Methods
- (NSUInteger)numberOfItemsInCarousel:(iCarousel *)carousel
{
if ([images count] == 1 || [self.carousel numberOfItems] == 1)
return 2;
return [images count];
}
- (NSUInteger)numberOfVisibleItemsInCarousel:(iCarousel *)carousel
{
//limit the number of items views loaded concurrently (for performance reasons)
return 7;
}
- (UIView *)carousel:(iCarousel *)_carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view
{
if (index >= [images count] || index >= [carousel numberOfItems]) {
index = 0;
}
NSDictionary *obj = [images objectAtIndex:index];
view = [[UIImageView alloc] initWithImage:[obj objectForKey:#"image"]];
view.tag = index;
return view;
}
- (NSUInteger)numberOfPlaceholdersInCarousel:(iCarousel *)carousel
{
//note: placeholder views are only displayed on some carousels if wrapping is disabled
return 0;
}
- (CGFloat)carouselItemWidth:(iCarousel *)carousel
{
//usually this should be slightly wider than the item views
return 400;
}
- (BOOL)carousel:(iCarousel *)carousel shouldSelectItemAtIndex:(NSInteger)index{
return 5;
}
- (BOOL)carouselShouldWrap:(iCarousel *)carousel
{
//wrap all carousels
return wrap;
}
Delete Method:
-(void) deleteItem{
//Removes the object chosen
if (carousel.numberOfItems >= 1)
{
NSInteger index = carousel.currentItemIndex;
[carousel removeItemAtIndex:index animated:YES];
//[images removeObjectAtIndex:index];
//[images replaceObjectAtIndex:index withObject:[NSNull null]];
}
}
What you could do is make
- (NSUInteger)numberOfItemsInCarousel:(iCarousel*)carousel;
return always a value >1 (eg. 2); then make sure that
- (UIView*)carousel:(iCarousel*)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView*)view;
always returns a correct view for the minimum number of elements you return from numberOfItemsInCarousel.
Eg.
- (NSUInteger)numberOfItemsInCarousel:(iCarousel*)carousel {
if (numberOfViews == 1)
return 2;
return numberOfViews;
}
- (UIView*)carousel:(iCarousel*)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView*)view {
if (index >= numberOfViews) {
index = 0;
}
NSDictionary *obj = [images objectAtIndex:index];
view = [[UIImageView alloc] initWithImage:[obj objectForKey:#"image"]];
view.tag = index;
return view;
}
You should also make sure that the last element is never deleted of add some guards in the code above to manage the case when no elements are left.
Hi have 4 component in my UIPickerView
When i am calling didSelectRow in UIPickerView.
When I am Rolling selectedRowInComponent:1
I am displaying NSLog(#"hours");
When I am Rolling selectedRowInComponent:0
I am displaying NSLog(#"date");
when i am scrolling selectedRowInComponent:1
It still display me date ... Not hours.
#any one help me out
Thanks in advance.
- (void)pickerView:(UIPickerView *)thePickerView
didSelectRow:(NSInteger)row
inComponent:(NSInteger)component
{
//get the values
NSInteger date_NewRow = [thePickerView selectedRowInComponent:0];
NSInteger hour_NewRow = [thePickerView selectedRowInComponent:1];
NSInteger minute_NewRow = [thePickerView selectedRowInComponent:2];
NSInteger am_pm_NewRow = [thePickerView selectedRowInComponent:3];
if (date_NewRow) {
NSLog(#"date");
}
else if (hour_NewRow) {
NSLog(#"hours");
}
//some code to save the values
}
IMHO you might be checking the wrong values. If you want to know which component changed, check the component variable:
if (component==0) NSLog(#"date");
else if (component==1) NSLog(#"hour");
else if (component==2) NSLog(#"minute");
else if (component==3) NSLog(#"am-pm");
Best,
Sascha
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,
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