I would like to show a set of consecutive numbers in a UIPickerView component but have it wrap around like the seconds component of the Clock->Timer application. The only behavior I can enable looks like the hours component of the Timer application, where you can scroll in only one direction.
It's just as easy to set the number of rows to a large number, and make it start at a high value, there's little chance that the user will ever scroll the wheel for a very long time -- And even then, the worse that will happen is that they'll hit the bottom.
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
// Near-infinite number of rows.
return NSIntegerMax;
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
// Row n is same as row (n modulo numberItems).
return [NSString stringWithFormat:#"%d", row % numberItems];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.pickerView = [[[UIPickerView alloc] initWithFrame:CGRectZero] autorelease];
// ...set pickerView properties... Look at Apple's UICatalog sample code for a good example.
// Set current row to a large value (adjusted to current value if needed).
[pickerView selectRow:currentValue+100000 inComponent:0 animated:NO];
[self.view addSubview:pickerView];
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
NSInteger actualRow = row % numberItems;
// ...
}
I found my answer here:
http://forums.macrumors.com/showthread.php?p=6120638&highlight=UIPickerView#post6120638
When it asks for the title of a row, give it:
Code:
return [rows objectAtIndex:(row % [rows count])];
When it says the user didSelectRow:inComponent:, use something like this:
Code:
//we want the selection to always be in the SECOND set (so that it looks like it has stuff before and after)
if (row < [rows count] || row >= (2 * [rows count]) ) {
row = row % [rows count];
row += [rows count];
[pickerView selectRow:row inComponent:component animated:NO];
}
It appears that the UIPickerView does not support wrapping around natively, but you can fool it by inserting more sets of data to be displayed and when the picker stops, centering the component to the middle of the data set.
Just create an array multiple times, so that you have your numbers multiple times. Lets say when want to have the numbers from 0 to 23 and put that in an array. that we will do 10 times like this...
NSString *stdStepper;
for (int j = 0; j<10; j++) {
for(int i=0; i<24; i++)
{
stdStepper = [NSString stringWithFormat:#"%d", i];
[_hoursArray addObject:stdStepper];
}
}
later we set the row 0 selected like this
[_hoursPickerView selectRow:120 inComponent:0 animated:NO];
Related
I would like to show a set of consecutive numbers in a UIPickerView component but have it wrap around like the seconds component of the Clock->Timer application. The only behavior I can enable looks like the hours component of the Timer application, where you can scroll in only one direction.
It's just as easy to set the number of rows to a large number, and make it start at a high value, there's little chance that the user will ever scroll the wheel for a very long time -- And even then, the worse that will happen is that they'll hit the bottom.
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
// Near-infinite number of rows.
return NSIntegerMax;
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
// Row n is same as row (n modulo numberItems).
return [NSString stringWithFormat:#"%d", row % numberItems];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.pickerView = [[[UIPickerView alloc] initWithFrame:CGRectZero] autorelease];
// ...set pickerView properties... Look at Apple's UICatalog sample code for a good example.
// Set current row to a large value (adjusted to current value if needed).
[pickerView selectRow:currentValue+100000 inComponent:0 animated:NO];
[self.view addSubview:pickerView];
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
NSInteger actualRow = row % numberItems;
// ...
}
I found my answer here:
http://forums.macrumors.com/showthread.php?p=6120638&highlight=UIPickerView#post6120638
When it asks for the title of a row, give it:
Code:
return [rows objectAtIndex:(row % [rows count])];
When it says the user didSelectRow:inComponent:, use something like this:
Code:
//we want the selection to always be in the SECOND set (so that it looks like it has stuff before and after)
if (row < [rows count] || row >= (2 * [rows count]) ) {
row = row % [rows count];
row += [rows count];
[pickerView selectRow:row inComponent:component animated:NO];
}
It appears that the UIPickerView does not support wrapping around natively, but you can fool it by inserting more sets of data to be displayed and when the picker stops, centering the component to the middle of the data set.
Just create an array multiple times, so that you have your numbers multiple times. Lets say when want to have the numbers from 0 to 23 and put that in an array. that we will do 10 times like this...
NSString *stdStepper;
for (int j = 0; j<10; j++) {
for(int i=0; i<24; i++)
{
stdStepper = [NSString stringWithFormat:#"%d", i];
[_hoursArray addObject:stdStepper];
}
}
later we set the row 0 selected like this
[_hoursPickerView selectRow:120 inComponent:0 animated:NO];
I'm programmatically coding STCollapseTableView. What I want is when user enters the particular screen, all headers must be expanded by default without clicking any headers. And I don't want other header to be collapsed if one is expanded. Is it possible? If not then what is other way to achieve this?
I have headers and if some of the header items contain child items, it should have expanded already.
Edit: I have figured out that I need to call below method in order to expand headers without clicking.
- (void)handleTapGesture:(UITapGestureRecognizer*)tap
{
NSInteger index = tap.view.tag;
if (index >= 0)
{
[self toggleSection:(NSUInteger)index animated:YES];
}
}
How can I call this method after all headers are displayed?
I use below code to return cell as header views for my table. So I need to call handleTapGesture() method after reloading tblItems.
[self.headers removeAllObjects];
for (int i = 0 ; i < [arrItems count] ; i++)
{
[self.headers addObject:[self addViewintoCell:i]];
}
[tblItems reloadData];
[tblItems setContentOffset:CGPointZero animated:NO];
Here are one lazy solution, but it couldn't be used as standard solution, that you open all section in loading just after reloading table with your data.
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView reloadData];
[self.tableView setExclusiveSections:NO];
//Get Section to open
NSInteger sectionCount = [self.tableView numberOfSections];
for (int needToOpen = 0; needToOpen<sectionCount; needToOpen++) {
//Open Section one by one
[self.tableView openSection:needToOpen animated:NO];
}
}
I'm making an iOS game that uses a modal view controller to let a user change settings for the game. If, for example, using a made up example, the user chooses 5 for the number of cats on the screen, I only want to let the user have no more than 4 bullets. If the user chooses 6 cats, then he/she can have up to 5 bullets. They can never have more bullets than cats. I am using UIPicker to set the number of cats and the number of bullets, so the Picker actually allows the user to set 3 cats, and 6 bullets. Therefore, when the modal unwinds, I want to be able to stop the unwind and present a message telling the user that there should be more cats than bullets etc.
Question:
1 I know how to compare the settings selected in the designated unwind method (a simple task), but how do I stop the modal from unwinding so that the user is forced to readjust the settings to comply with game rules.
2 Is there a way in the modal to only show the number of bullets after the user has selected the number of cats and to therefore limit the number of bullets available based on the number of cats selected?
Update. I'm able to call pickerView:didSelectRow:inComponent after selection of the number of cats, but I'm not sure how to use that to limit the number of bullets. The code below doesn't work
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
if(component == 0)
{
return [ _cats objectAtIndex:row];
[self pickerView:self didSelectRow:row inComponent:0];
}
else
{
return [_bullets objectAtIndex:row];
}
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
if (row == 3){
_bullets = #[#"1",#"2"]; //this part isn't working. not sure how to limit
//number of bullets based on row selected
}else if (row == 4){
_bullets = #[#"1", "2", "3"];
}
....
}
Per the comments, here's an example view controller implementation to correct a bad selection before the unwind happens:
#import "CNBViewController.h"
#implementation CNBViewController {
UIPickerView * _picker;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
_picker = [UIPickerView new];
_picker.delegate = self;
_picker.dataSource = self;
[_picker setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:_picker];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:_picker attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:20]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:_picker attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
}
return self;
}
#pragma mark - UIPickerViewDataSource methods
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
// Two components, cats and bullets
return 2;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
if ( component == 0 ) {
// Let's have 10 rows for cats...
return 10;
}
else {
// ...and 6 rows for bullets.
return 6;
}
}
#pragma mark - UIPickerViewDelegate methods
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
// Just populate with numbers from 2 up for cats (component 0) and
// 1 up for bullets (component 1), otherwise we could end up with both
// being 1 which violates our constraint.
int offset;
if ( component == 0 ) {
offset = 2;
}
else {
offset = 1;
}
return [NSString stringWithFormat:#"%d", row + offset];
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
if ( component == 0 ) {
// Cats component changed, so get selected index of bullets component
NSInteger bullets = [pickerView selectedRowInComponent:1];
// If bullets is now greater than cats, reduce bullets
if ( bullets > row ) {
[pickerView selectRow:row inComponent:1 animated:YES];
}
}
else if ( component == 1 ) {
// Bullets component changed, so get selected index of cats component
NSInteger cats = [pickerView selectedRowInComponent:0];
// If bullets is now greater than cats, reduce bullets
if ( row > cats ) {
[pickerView selectRow:cats inComponent:1 animated:YES];
}
}
}
#end
Notes:
The header file for the view controller should declare the class as implementing the UIPickerViewDataSource and UIPickerViewDelegate protocols.
For simplicity, there is no unwind going on, and I've just shown how to make the picker view behave.
I've set the cats component to just show values from 2 upwards, and the bullets component to start from 1 upwards. If bullets must always be less than cats, and bullets starts at 1, it just makes no sense to have cats start any lower than 2, since it would never be a valid selection. When the selected row in each component is the same, therefore, our condition (that bullets must show no greater than cats less one) is satisfied, since if the selected row is 3 in both cases, then cats will display 5, and bullets will display 4. The condition is not satisfied if the selected row of the bullets component is greater than the selected row of the cats component.
In pickerView:didSelectRow:inComponent:, if it's the cats component that changed, we just look at the value of the selected index in the bullets component, and if it exceeds that of cats, we set it to equal that of cats, to restore our constraint.
If it's the bullets component that changed, we look at the value of the selected index in the cats component, and if it exceeds that of cats, again, we set it equal to that of cats, effectively undoing the change we've just been notified about.
Try playing around with it, changing either component to make the value of the right component greater than or equal to the left, and when you release the touch, you'll see it change to an acceptable value.
I have found similar questions here and here. I have tried the answers to these and other questions found on stackoverflow and Apple's developer forums, with no luck.
I created a fresh project to test this, and it works fine in that new project. I am iteratively removing differences between my new test project and the one exhibiting this issue. Any pointers would be most helpful.
I have a UIPickerView connected to my view controller. I want to initialize the UIPickerView to the last value selected by the user. However if this row is the last row in the picker, it shows the second-to-last row selected in the UI instead.
Meanwhile, logging shows the correct row index selected in the UIPickerView object instance.
Selecting any row besides the last row in the picker works fine.
I have tried calling the selectRow:inComponent:animated method from viewDidLoad, viewWillAppear, and viewDidAppear - none of these make any difference. I have also tried calling reloadAllComponents in various orders relative to row selection and data initialization - again, no difference.
Some code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSParameterAssert(self.pickerView);
// Set pickerView data
self.pickerOptions = #[#"zero", #"one", #"two", #"three", #"four"];
// Picker View config
self.pickerView.dataSource = self;
self.pickerView.delegate = self;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSInteger selectedIndex = ([self.pickerOptions count] -1);
NSLog(#"About to select row: %d", selectedIndex);
[self.pickerView selectRow:selectedIndex inComponent:0 animated:NO];
NSLog(#"Selected value is %d", [self.pickerView selectedRowInComponent:0]);
}
#pragma mark UIPickerViewDataSource
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
NSInteger numRows = [self.pickerOptions count];
NSLog(#"numRows: %d", numRows);
return numRows;
}
#pragma mark UIPickerViewDelegate
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
NSString *rowTitle = [self.pickerOptions objectAtIndex:(NSUInteger)row];
return rowTitle;
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
NSString *value = [self.pickerOptions objectAtIndex:(NSUInteger)row];
NSLog(#"User selected value: %#", value);
NSLog(#"index: %d", row);
}
Log output:
-[CCSelectorViewController viewDidAppear:] About to select row index: 4
-[CCSelectorViewController pickerView:numberOfRowsInComponent:] numRows: 5
-[CCSelectorViewController viewDidAppear:] Selected row index is 4
Here is the screenshot of the wrong row being selected:
http://imgur.com/FVvu9RA
This is using iOS6, XCode 4.6.1.
Any ideas?
I'm attempting to update a single UIPickerView with a different NSArray of data based on which Index is selected from a UISegmentedControl. Currently when I change the control the numberOfRowsInComponent does not update, and the titleForRow will only update when scrolling the picker.
The NSArrays are populated within viewDidLoad, and I'm using the reloadAllComponents method upon an IBAction of the SegmentedControl.
#synthesize subnetView, classControl;
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
//One column
return 1;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
//set number of rows
if (classControl.selectedSegmentIndex == 0){
NSLog(#"Class A Rows %d", [classAArray count]);
return classAArray.count;
}
else if (classControl.selectedSegmentIndex == 1){
return classBArray.count;
}
else if (classControl.selectedSegmentIndex == 2){
return classCArray.count;
}
return 0;
}
-(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
//set item per row
if (classControl.selectedSegmentIndex == 0){
NSLog(#"Class A Rows %d", [classAArray count]);
return [classAArray objectAtIndex:row];
}
else if (classControl.selectedSegmentIndex == 1){
return [classBArray objectAtIndex:row];
}
else if (classControl.selectedSegmentIndex == 2){
return [classCArray objectAtIndex:row];
}
return 0;
}
-(IBAction)classChange{
[subnetView reloadAllComponents];
}
Based on which selector is chosen to be "selected" within interface builder, the picker is loaded with the correct array and number of rows. Based on this code when selecting an array with less elements, the numberOfRowsInComponents is not being updated, and the app will crash when reaching the end of the smaller array.
So my two problems:
Updating of elements only occurs when scrolling.
The number of rows does not update when performing the reloadAllComponents method.
Thanks for listening!
I've seen this before. Usually it is caused by the pickerview outlet not being connected, effectively calling reloadAllComponents on nothing. But when you scroll the connected data source and delegate methods still work.
This can be easily checked by login the outlet's value using:
NSLog(#"%#",subnetView);
If it logs (NULL) as I expect it will simply connect your IB outlet and you're done.