** Update! **
Make sure to read part two of this article to learn about how this solution was optimized.
When I was building iAutoCalc for the iPhone I was looking for an easy way to validate and format the data as it was entered into a UITextField. I found plenty of articles to do the validation, but nothing that showed a way to arbitrarily format the field as data was entered. After tinkering around I came up with a pretty neat method of providing a locale correct format for currency data. I haven't played with it anymore yet for other format styles, but I think it could be easily adopted for any format style.
The exact problem I wanted to solve was to have the user only be allowed to enter numeric data and have that data formatted as a local specific currency with proper grouping separators. For example, if the user entered 20000 the UITextField would display $20,000. I also wanted the formatting to happen real time as the user entered the data.
First we need to create a currency formatter:
The above creates a basic, local specific currency formatter without any decimal places.
We also need to create a set to help us reject all non-numbers:
I create both the currencyFormatter and nonNumberSet in the init function for use in the text change callback method below.
Now we need a hook into the UITextField that will tell our controller know when the text has been updated. The UITextFieldDelegate does just that by providing the callback method:
Each time a character is typed into the UITextField the above function will execute prior to the character being entered. If the function returns YES the character will be entered into the UITextField. For more information on this method see the Apple Developer Documentation.
Below is the full listing of the completed function:
The things to note about the finished function is that I am always returning NO from the function. I do this because I am manually managing what characters end up in the text field.
Next, notice the odd empty string case where I put in the localized currency symbol. The problem is that the currency formatter will not convert a string to a number without a currency symbol. It doesn't care about grouping symbols, but the lack of a currency symbol will cause it to return nothing. This case makes sure that the currency symbol is always the first character in the text field.
Finally is the trick that properly places the localized grouping separators. After making the correct string by inserting or removing characters based on the information the callback passes in, we turn that string into its number representation and then turn around and format it right back into a string. This process removes any grouping symbols (localized of course) and then puts them back in in the correct locations.