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.
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];
In my initial view controller that is loaded, one of my subviews is a UITextField. I have other subviews of the main view as well (i.e. four labels, one pickerview, and two buttons). When I try to edit the textfield, all of the other subviews shift to the right. I have no idea why this is happening. I have no code even applying to what happens while editing the textfield. But, I do have some code that serves to align all of my subviews (including the textfield). Here is that code:
- (void)viewDidLoad {
[super viewDidLoad];
self.stopPickerView.dataSource = self;
self.stopPickerView.delegate = self;
selectedEntry=#"";
self.inputField.delegate=self;
// Do any additional setup after loading the view, typically from a nib.
}
-(void)viewDidAppear:(BOOL)animated{
int width=self.view.bounds.size.width;
int height=self.view.bounds.size.height;
headingLabel.frame=CGRectMake(headingLabel.frame.origin.x, headingLabel.frame.origin.y,self.view.bounds.size.width-self.view.bounds.size.width/5, headingLabel.frame.size.height);
headingLabel.center=CGPointMake(width/2,height/9);
stopPickerView.center=CGPointMake(width/2, height/5);
setStop.center=CGPointMake(width/2, height/2.2);
orLabel.center=CGPointMake(width/2, height/1.7);
wakeUpLabelOne.frame=CGRectMake(wakeUpLabelOne.frame.origin.x, wakeUpLabelOne.frame.origin.y,self.view.bounds.size.width-self.view.bounds.size.width/2, self.view.bounds.size.height-self.view.bounds.size.height/19.2);
wakeUpLabelOne.center=CGPointMake(width/3.5, height/1.4);
NSLog(#"%f",wakeUpLabelOne.frame.size.width);
wakeUpLabelTwo.center=CGPointMake(width/1.1, height/1.4);
inputField.center=CGPointMake(width/1.5, height/1.4);
finalSetButton.center=CGPointMake(width/2, height/1.2);
[wakeUpLabelTwo adjustsFontSizeToFitWidth];
}
- (int)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 1;
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
selectedEntry = [_pickerData objectAtIndex:row];
}
// The number of rows of data
- (int)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
return _pickerData.count;
}
// The data to return for the row and component (column) that's being passed in
- (NSString*)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
return _pickerData[row];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[inputField resignFirstResponder];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return NO;
}
If you have any ideas to why this is happening or you would like more information, just leave it in the comments.
I figured out why this was happening. For some reason, putting my frame configurations in the viewDidAppear method was a bad idea and was messing something up. The fix to this was to just put my frame configurations in the viewDidLayoutSubviews method instead. The problem was immediately fixed.
It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
I am setting up a UIPickerView to have choices like choice a, choice b, choice c and so on. I have tried to interpret the sample code from Apple but that seems very difficult to understand for a beginner like me. I also would like the choices from the picker view to take me to another page if that is possible.
It's obvious for every beginner that it is some what tedious to understand these things the first time.
Anyways, do you know how to use UITableViews? Do you know how to use UITableViewDelegate and UITableViewDataSource? If your answer is yes, then just imagine UIPickerViews are like UITableViews (but remember they are not UITableViewControllers).
Let's say, I've a UIPickerView:
UIPickerView *objPickerView = [UIPickerView new]; // You need to set frame or other properties and add to your view...you can either use XIB code...
1) First you need to assign the delegate and dataSource to the UIPickerView either via IB or code. It depends on your implementation (So this step looks very similar to a UITableView, doesn't it?)
Like this:
objPickerView.delegate = self; // Also, can be done from IB, if you're using
objPickerView.dataSource = self;// Also, can be done from IB, if you're using
2) Next, you need to define number of sections, like this:
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)thePickerView {
return 1; // Or return whatever as you intend
}
2) Then you need to define the number of rows you need:
- (NSInteger)pickerView:(UIPickerView *)thePickerView
numberOfRowsInComponent:(NSInteger)component {
return 3;//Or, return as suitable for you...normally we use array for dynamic
}
3) Then, define title for row (And if you have multiple section, then title for each section):
- (NSString *)pickerView:(UIPickerView *)thePickerView
titleForRow:(NSInteger)row forComponent:(NSInteger)component {
return [NSString stringWithFormat:#"Choice-%d",row];//Or, your suitable title; like Choice-a, etc.
}
4) Next, you need to get the event when someone clicks on an element (As you want to navigate to other controller/screen):
- (void)pickerView:(UIPickerView *)thePickerView
didSelectRow:(NSInteger)row
inComponent:(NSInteger)component {
//Here, like the table view you can get the each section of each row if you've multiple sections
NSLog(#"Selected Color: %#. Index of selected color: %i",
[arrayColors objectAtIndex:row], row);
//Now, if you want to navigate then;
// Say, OtherViewController is the controller, where you want to navigate:
OtherViewController *objOtherViewController = [OtherViewController new];
[self.navigationController pushViewController:objOtherViewController animated:YES];
}
That's all the implementation you need.
I'll briefly explain how to achieve that programmatically
- (void)viewDidLoad {
[super viewDidLoad];
UIPickerView * picker = [UIPickerView new];
picker.delegate = self;
picker.dataSource = self;
picker.showsSelectionIndicator = YES;
[self.view addSubview:picker];
}
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
return 3;
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
NSString * title = nil;
switch(row) {
case 0:
title = #"a";
break;
case 1:
title = #"b";
break;
case 2:
title = #"c";
break;
}
return title;
}
Basically in the viewDidLoad we are creating and adding a UIPickerView to the view, telling it that our controller serves both as delegate and dataSource (You can do this in Interface Builder too)
Then we are implementing two data source methods in order to tell how many components and how many rows per components the pickerView has, respectively numberOfComponentsinPickerView: and pickerView:numberOfRowsInComponent:
Finally we are implementing the delegate method pickerView:titleForRow:forComponent: that returns the content of every line.
If you want to customize the behavior when a row has been selected you can implement the delegate method pickerView:didSelectRow:inComponent: which - as the name suggests - is called every time a row is selected.
A UIPickerView use a parten similar to the one use for a UITableView. DataSource and Delegate protocol.
The DataSource methods are used to tell the picker how many 'columns' and how many 'rows' there are in your picker.
While the Delegate methods are for the rest, height of rows, width of columns, views or title to display and a call back when the picker got moved.
UIPickerView
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.
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];