Wednesday, September 14, 2011

iOS: Setting the default on a UIPickerView!

Welcome to my blog! If you've stumbled across this in your travels on the internet, please take a moment click on an ad - or two! You might see something you like, *and* it helps finance the blogosphere. Think of the children!

Ok, we're making some good progress here. After getting somewhat hung up on the numeric keyboard issue, it seems we've finally got it solved. Next up is completing the initial validation logic, which will be performed when the "save" button is pressed. We've already got some logic from the Android app that we're converting. Let's take a look.

We've already got this, it's an easy one:

if (quizStartNum < 1) { // etc. see previous post.

Ok, now what we want to do is something a bit more complex. Actually, I haven't implemented the paid vs. free logic yet. I'll hold that off till later as a separate task. So, for now what I want to do is find the highest number for the current level.

Actually, I should go back up an clean up the level-setting logic. I'm doing something wrong, because right now it's just getting stuck on level 5 even when I change it.

Here's the code in question:



//[pickerView selectRow:4 inComponent:0 animated:NO];
mlabel.text= [arrayNo objectAtIndex:[pickerView selectedRowInComponent:0]];


I'm not sure why it's defaulting to the last row, but in any case the goal is to retrieve the value from prefs. If it's null or zero or whatever, then we want to set it to say, 3, which is the middle level and the same thing I'm doing with the Android app. Otherwise, we'll use the level in prefs. We already have code for that:


/*
myInteger = 0;

NSUserDefaults *prefs2 = [NSUserDefaults standardUserDefaults];

// Get last state if existing
prefs2 = [NSUserDefaults standardUserDefaults];

myInteger = [prefs2 integerForKey:@"jlptLevel"];

NSLog(@"myInteger: %d ", myInteger );
*/
}



This post in SO gives good advice on how to set up a constant:

http://stackoverflow.com/questions/538996/constants-in-objective-c

Well, setting up a separate file may be a bit overkill, since we'll probably only need it in this file/compilation unit.


There we go:

// in the .m file
static NSInteger const DEFAULT_JLPT_LEVEL = 3;




And here's the code:


NSInteger jlptLevel;;

NSUserDefaults *prefs2 = [NSUserDefaults standardUserDefaults];

jlptLevel = [prefs2 integerForKey:@"jlptLevel"];

NSLog(@"myInteger: %d ", jlptLevel );

if ( (jlptLevel > 0) && (jlptLevel <= 5) ){
[pickerView selectRow:jlptLevel inComponent:0 animated:NO];
}
else {
[pickerView selectRow:DEFAULT_JLPT_LEVEL inComponent:0 animated:NO];
}



Hmmm...it's still not budging off the five - let's check the save logic.

Actually, the problem was this method, which I've corrected:


- (NSString *)pickerView:(UIPickerView *)pickerViewParm titleForRow:(NSInteger)row forComponent:(NSInteger)component;
{

NSInteger jlpLevel = 0;

NSUserDefaults *prefs2 = [NSUserDefaults standardUserDefaults];

// Get last state if existing
prefs2 = [NSUserDefaults standardUserDefaults];

jlpLevel = [prefs2 integerForKey:@"jlptLevel"];

NSLog(@"myInteger: %d ", jlpLevel );

[pickerViewParm selectRow:(jlpLevel-1) inComponent:0 animated:YES];

return [arrayNo objectAtIndex:row];
}



Well, somehow I'm getting a SIGABRT at the main loop. On thing I don't like is it doesn't give you a stack trace.


Ok, well, if you look at the error message, it means that there was another process running. So, I stopped the simulator, and now it's running again.

Hmmm - well at first I got some changes, but they were inconsistent, then it settled on 5. Hmm.

I'm thinking I need a class level instance for prefs, instead of the local one.

Ok, I think the problem is that

- (NSString *)pickerView:(UIPickerView *)pickerViewParm titleForRow:(NSInteger)row forComponent:(NSInteger)component;


Get's called every time I select the row, not just on default.

Well, if I just comment it out - I get all question marks showing up in the picker view.

According to the delegate doc, this is what that method is for:

pickerView:titleForRow:forComponent:

Called by the picker view when it needs the title to use for a given row in a given component.


The title? WTF? What is the title? What about the value?

Anyway, when I bring it down to this:


- (NSString *)pickerView:(UIPickerView *)pickerViewParm titleForRow:(NSInteger)row forComponent:(NSInteger)component;
{
NSLog(@"Int pickerView:titleForRow");

return [arrayNo objectAtIndex:row];
}


it's not causing me hassles any more because it's not pulling from the preserved state.


But now I lose the default value feature. Unless - I just call the state retrieval on the first time into the method.

Ok, I definitely get a bad access in the main loop if I take this line out:



return [arrayNo objectAtIndex:row];


So, this title method is really the one redisplays the text. It needs to be called every time.

Actually, for some reason, title for row is called a couple of times.

No, even when I account for that, sets the text, or title to the right number - but for all the rows, and it is still stuck on row zero. So, I don't know what the answer is, but it's *not* that. No, that's not quite right the problem was I was returning the jlpt level. The values will be ok if I return the row value. The question is, why isn't it changing the selected row? It was doing it before, when I was hardcoding it to the value of 4. Let's see if I can get back to that.

Try this again:

[pickerView selectRow:4 inComponent:0 animated:NO];



Well, I'll be damned. I went to the xib file, and it didn't show it as connected! I should've checked earlier. Man, that xib thing is undependable. Sheesh.

The problem is that if you set the default row value, then it just comes back to this method. So the counter idea goes down.

At last - I gave up and just started googling again, and came across this:

http://www.iphonedevsdk.com/forum/iphone-sdk-development/11594-set-selected-row-uipickerview-applicationdidfinishlaunching.html

here is a simple answer:

If you do this in

-(void) viewWillAppear: (BOOL) animated {
[yourPickerViewName selectRow:yourRow inComponent:yourComponent animated:YES]
}

it will work. If you do it in viewWillLoad, it does not work.
snorkeler is offline Reply With Quote


Ok, if I do this:


-(void) viewWillAppear: (BOOL) animated {
NSInteger jlptLevel;;

prefs = [NSUserDefaults standardUserDefaults];

jlptLevel = [prefs integerForKey:@"jlptLevel"];

NSLog(@"jlptLevel from prefs: %d ", jlptLevel );

if ( (jlptLevel > 0) && (jlptLevel <= 5) ){
[pickerView selectRow:jlptLevel inComponent:0 animated:YES];
}
else {
[pickerView selectRow:DEFAULT_JLPT_LEVEL inComponent:0 animated:YES];
}
[pickerView selectRow:4 inComponent:0 animated:YES];
}



it works, because of the hardcode on the last line. But, without the hardcode it's, not working. Could NSInteger be the problem? Nope, int doesn't work either.

At the least I have a solution for a seriously hacked workaround. It's the best I'm gonna do for now, until I find something better. And it works:


-(void) viewWillAppear: (BOOL) animated {


NSInteger jlptLevel;

// retreive the stored level from prefs

prefs = [NSUserDefaults standardUserDefaults];

jlptLevel = [prefs integerForKey:@"jlptLevel"];


NSLog(@"jlptLevel from prefs: %d ", jlptLevel );


// set the value

if ( (jlptLevel > 0) && (jlptLevel <= 5) ){

// switch needed because select row with variable doesn't work

switch(jlptLevel)
{
case 1:
[pickerView selectRow:0 inComponent:0 animated:YES];
break;
case 2:
[pickerView selectRow:1 inComponent:0 animated:YES];
break;
case 3:
[pickerView selectRow:2 inComponent:0 animated:YES];
break;
case 4:
[pickerView selectRow:3 inComponent:0 animated:YES];
break;
case 5:
[pickerView selectRow:4 inComponent:0 animated:YES];
break;
default:
break;
}
}

else {

// default to level 3
[pickerView selectRow:2 inComponent:0 animated:YES];
}

}

1 comment:

  1. Excellent! I had the same problem and this solved it for me.

    ReplyDelete