Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

Qaanol

macrumors 6502a
Original poster
Jun 21, 2010
571
11
I have a text field for the user to enter a number. Specifically, an integer. So I made a nice little action and hooked the text field up to call it when done editing. It looks like this:

Code:
- (IBAction)editNumber:(id)sender {
    [sender setIntegerValue:[sender integerValue]];
}

Simple right? It turns the text into an integer, and sets the text field to show that integer. It works great: you enter 6, it shows 6. You enter ‘abc’, it shows 0. You enter 3.14159, it shows 3.

When you enter a number with 4+ digits, so anything 1000 and up, it adds commas. So if you type 1234567, it shows ‘1,234,567’. I like that feature a lot. It makes it easy to read.

However…once the text field has the commas, for example ‘1,000’, then when you click away or hit enter, it loses everything from the first comma on. So ‘4,096’ turns into 4. Kind of a problem.

I know I could use a number formatter to do what I want, but that is kind of overkill for such a simple task. I could also get the string value from the text field and remove all commas first…but that seems like a hassle.

So…I guess my question is, why is there an asymmetry in the NSTextField class, whereby “integerValue” chokes on commas but “setIntegerValue” inserts them?
 
Probably because one of the methods is local (country) aware while the other isn't. The later i.e. NSStrings -integerValue method, appears to only look for the first sequence of number characters after which it discards any trailing "junk" characters.
 
From an MVC perspective, maybe try thinking of architecting in a different way. What you want is a canonical representation of the integer your user entered. My advice would be to keep that integer saved at your model or controller level.

You could have the delegate callback from the text field update the integer value at your model level. Your controller could then notify the text field that the value has changed and pass the value. It looks like an extra step. It is an extra step. The simplicity you lose from before is worth it if you can find a more robust solution.

To put it another way, we have all been tempted to keep data in our views because it meant saving an extra step of code. It is almost always a false economy that you end up paying for later in other ways.
 
From an MVC perspective, maybe try thinking of architecting in a different way. What you want is a canonical representation of the integer your user entered. My advice would be to keep that integer saved at your model or controller level.

You could have the delegate callback from the text field update the integer value at your model level. Your controller could then notify the text field that the value has changed and pass the value. It looks like an extra step. It is an extra step. The simplicity you lose from before is worth it if you can find a more robust solution.

To put it another way, we have all been tempted to keep data in our views because it meant saving an extra step of code. It is almost always a false economy that you end up paying for later in other ways.

That does not actually solve the problem. Let us suppose that I keep the data in an NSNumber object within the model. How do I display the number in the text field as an integer?

Well, I have the controller say, “Hey model, what integer does your NSNumber represent?” And then take that integer to the text field and say, “Hey text field, show this integer.” In code, that looks like this:

Code:
NSInteger n = [[[self modelObject] storedNumber] integerValue];
[[self textField] setIntegerValue:n];

Now how do I update the number when the user finishes editing the text field?

Well, what I want to do is have the controller say, “Hey text field, what integer did the user type in?” And then take that integer and say, “Hey model, update your NSNumber to store this value.” In code that looks like this:

Code:
NSInteger n = [[self textField] integerValue];
[[self modelObject] setStoredNumber:[NSNumber numberWithInteger:n]];

Putting it all together, I want to have an action that gets called when the user finishes editing the text field, which stores to the model the new integer value that the user typed and then displays it in the text field. That looks like this:

Code:
- (IBAction)editNumber:(id)sender {
    NSInteger n = [sender integerValue];
    [[self modelObject] setStoredNumber:[NSNumber numberWithInteger:n]];
    NSInteger m = [[self modelObject storedNumber] integerValue];
    [sender setIntegerValue:m];
}

Here the line that creates m is redundant, because (m==n) will always be true. We could remove m and just use n. And the reason we can do that, is that an NSNumber object preserves the value of the integer. The problem is that an NSTextField does not.

If the user types in “16384” and hits return, the NSNumber will be created with integer value 16384. The integer value 16384 will be retrieved from the NSNumber. The integer value 16384 will be passed to the NSTextField. And the NSTextField will display “16,384”. That part works exactly as intended and as expected.

Now the NSTextField shows “16,384”, and the user has not made any additional edits. Suppose the user presses return again. The NSNumber will be created with integer value 16. The integer value 16 will be retrieved from the NSNumber. The integer value 16 will be passed to the NSTextField. And the NSTextField will display “16”.

Do you see the problem? The correct value of 16384 was stored to the model the first time, and passed back to the UI for display. The user made no further edits to the text field. And the text field acted as if the user had entered a different number, which the controller dutifully passed on to the model because the UI was acting as though the user had made that change.

I am not trying to store data in the UI, I am only trying to retrieve user input from the UI, updated the data stored in the model accordingly, and then display the stored data in the UI. That seems to me precisely what a controller is supposed to do. Thus I am rather flustered that when the user enters certain numbers and presses return twice without changing anything in between, the model gets modified a second time and ends up storing a value which the user never entered.

My work-around ended up being to put a category on NSTextField with an -integerValueIgnoreCommas method. This is not ideal, as it simply removes all commas before getting the integerValue, but it does work for my purposes.
 
...
I know I could use a number formatter to do what I want, but that is kind of overkill for such a simple task. I could also get the string value from the text field and remove all commas first…but that seems like a hassle.

Maybe it's not overkill.

There seems to be a default formatter, and it's producing commas. You should be able to verify this, maybe by checking the documentation, or perhaps with the debugger.

Sadly, either the formatter isn't correctly parsing commas, or it's not being used in a way that it should.

I suggest setting a formatter on the field and confirming what actually happens. By setting an explicit formatter, you should be able to easily confirm whether the formatter is being used to produce the string and/or to parse the string. Once the behavior is confirmed, you can take the next step, which is to make it parse the string properly.

The steps are basically:
1. Find out exactly what's happening.
2. Apply corrective action.

You can only do step #1 by discovering whether a formatter is being applied, or whether there's a formatter at all. If not, the simplest corrective I can think of is to apply a formatter. If a default formatter is being applied, the simplest corrective I can think of is to change to a different formatter.

You may have to experiment with different formats to get the correct behavior. That seems like less overkill to me than adding a category on the field. YMMV.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.