What is the best way to enter numeric values with decimal points? - iphone

In my app users need to be able to enter numeric values with decimal places. The iPhone doesn't provides a keyboard that's specific for this purpose - only a number pad and a keyboard with numbers and symbols.
Is there an easy way to use the latter and prevent any non-numeric input from being entered without having to regex the final result?
Thanks!

I think it would be good to point out that as of iOS 4.1 you can use the new UIKeyboardTypeDecimalPad.
So now you just have to:
myTextField.keyboardType=UIKeyboardTypeDecimalPad;

A more elegant solution happens to also be the simplest.
You don't need a decimal separator key
Why? Because you can simply infer it from the user's input. For instance, in the US locale when you what to enter in $1.23, you start by entering the numbers 1-2-3 (in that order). In the system, as each character is entered, this would be recognized as:
user enters 1: $0.01
user enters 2: $0.12
user enters 3: $1.23
Notice how we inferred the decimal separator based on the user's input. Now, if the user wants to enter in $1.00, they would simply enter the numbers 1-0-0.
In order for your code to handle currencies of a different locale, you need to get the maximum fraction digits of the currency. This can be done with the following code snippet:
NSNumberFormatter *currencyFormatter = [[NSNumberFormatter alloc] init];
[currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
int currencyScale = [currencyFormatter maximumFractionDigits];
For example, the Japanese yen has a maximum fraction digit of 0. So, when dealing with yen input, there is no decimal separator and thus no need to even worry about fractional amounts.
This approach to the problem allows you to use the stock numeric input keypad provided by Apple without the headaches of custom keypads, regex validation, etc.

Here is an example for the solution suggested in the accepted answer. This doesn't handle other currencies or anything - in my case I only needed support for dollars, no matter what the locale/currency so this was OK for me:
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string {
double currentValue = [textField.text doubleValue];
//Replace line above with this
//double currentValue = [[textField text] substringFromIndex:1] doubleValue];
double cents = round(currentValue * 100.0f);
if ([string length]) {
for (size_t i = 0; i < [string length]; i++) {
unichar c = [string characterAtIndex:i];
if (isnumber(c)) {
cents *= 10;
cents += c - '0';
}
}
} else {
// back Space
cents = floor(cents / 10);
}
textField.text = [NSString stringWithFormat:#"%.2f", cents / 100.0f];
//Add this line
//[textField setText:[NSString stringWithFormat:#"$%#",[textField text]]];
return NO;
}
The rounds and floors are important a) because of the floating-point representation sometimes losing .00001 or whatever and b) the format string rounding up any precision we deleted in the backspace part.

I wanted to do exactly the same thing, except with currencies rather than straight decimal values.
I ended up creating a custom view which contains a UITextField and a UILabel. The label covers the text field, but the text field still receives touches. I use the UITextFieldTextDidChange notification to observe changes in the text field (and I used a NSNumberFormatter to turn the resulting number into a formatted currency value) to update the label.
To disable the loupe that allows the user to reposition the insertion point, you'll need to use a custom UITextField subclass and override touchesBegan:withEvent: and set it to do nothing.
My solution might be different from what you need because the decimal point is always fixed -- I use the system's currency setting to determine how many there digits ought to be after the decimal point. However, the numeric keypad doesn't have a decimal point on it. And you can't add any buttons to the keyboard (which is especially aggravating because there's a blank button in the lower-left corner of the keyboard that would be perfect for a decimal point!) So I don't have a solution for that, unfortunately.

Depending on the specific application, providing a slider that the user can select a position from might be a better choice on the iphone. Then no digits need to be entered at all.

You may want to use a slider (as suggested by Martin v. Löwis) or a UIPickerView with a separate wheel for each of the digits.

I built a custom Number pad view controller with decimal point... check it out:
http://sites.google.com/site/psychopupnet/iphone-sdk/tenkeypadcustom10-keypadwithdecimal

As of iOS4.1, there is a new keyboard type UIKeyboardTypeNumberPad, but unfortunately, as of yet it doesn't seem to appear in the Interface Builder pick list.

Here's how to do it without using floats, round() or ceil() in a currency agnostic manner.
In you view controller, set up the following instance variables (with associated #property statements if that's your bag):
#interface MyViewController : UIViewController <UITextFieldDelegate> {
#private
UITextField *firstResponder;
NSNumberFormatter *formatter;
NSInteger currencyScale;
NSString *enteredDigits;
}
#property (nonatomic, readwrite, assign) UITextField *firstResponder;
#property (nonatomic, readwrite, retain) NSNumberFormatter *formatter;
#property (nonatomic, readwrite, retain) NSString *enteredDigits;
#end
and your viewDidLoad method should contain the following:
- (void)viewDidLoad {
[super viewDidLoad];
NSNumberFormatter *aFormatter = [[NSNumberFormatter alloc] init];
[aFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
currencyScale = -1 * [aFormatter maximumFractionDigits];
self.formatter = aFormatter;
[aFormatter release];
}
Then implement your UITextFieldDelegate methods as follows:
#pragma mark -
#pragma mark UITextFieldDelegate methods
- (void)textFieldDidBeginEditing:(UITextField *)textField {
// Keep a pointer to the field, so we can resign it from a toolbar
self.firstResponder = textField;
self.enteredDigits = #"";
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
if ([self.enteredDigits length] > 0) {
// Get the amount
NSDecimalNumber *result = [[NSDecimalNumber decimalNumberWithString:self.enteredDigits] decimalNumberByMultiplyingByPowerOf10:currencyScale];
NSLog(#"result: %#", result);
}
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
// Check the length of the string
if ([string length]) {
self.enteredDigits = [self.enteredDigits stringByAppendingFormat:#"%d", [string integerValue]];
} else {
// This is a backspace
NSUInteger len = [self.enteredDigits length];
if (len > 1) {
self.enteredDigits = [self.enteredDigits substringWithRange:NSMakeRange(0, len - 1)];
} else {
self.enteredDigits = #"";
}
}
NSDecimalNumber *decimal = nil;
if ( ![self.enteredDigits isEqualToString:#""]) {
decimal = [[NSDecimalNumber decimalNumberWithString:self.enteredDigits] decimalNumberByMultiplyingByPowerOf10:currencyScale];
} else {
decimal = [NSDecimalNumber zero];
}
// Replace the text with the localized decimal number
textField.text = [self.formatter stringFromNumber:decimal];
return NO;
}
Only tested this with pounds and pence, but it should work with Japanese Yen too. If you want to format decimals for non-currency purposes, then just read the documentation on NSNumberFormatter and set whatever format/maximumFractionDigits you want.

A Swift 2 implementation of Mike Weller's post, also only USD:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
guard let str = textField.text else {
textField.text = "0.00"
return false
}
let value = (str as NSString).doubleValue
var cents = round(value * 100)
if string.characters.count > 0 {
for c in string.characters {
if let num = Int(String(c)) {
cents *= 10
cents += Double(num)
}
}
}
else {
cents = floor(cents / 10)
}
textField.text = NSString(format: "%.2f", cents/100) as String
return false
}

You can use STATextField and set currencyRepresentation to YES which:
Ensures no more than one decimal point is entered and that no more than 2 digits are entered to the right of said decimal point.
There's also STAATMTextField which supports currency mode and ATM text entry by default:
Provides a text field that mimics ATM machine input behavior.

Related

Based on number of lines in uitextview getting substring

Frrom UITextview i am able to get number of lines for given text, if its more than 4 lines i want to truncate the rest, so how to get the index to substring?
It will restrict to user to enter more than 4 lines in a UITextview
- (BOOL)textView:(UITextView *)aTextView shouldChangeTextInRange:(NSRange)aRange replacementText:(NSString*)aText
{
if (textView.contentSize.height/textView.font.lineHeight>4) {
return NO;
}
else
return YES;
}
NSMutableString *str = [[NSMutableString alloc] initWithString:textView.text];
static int numberOfLines = 0;
int nOC = 1;
while (nOC < [textView.text count] && numberOfLines < 4) {
if ([[str substringWithRange:NSMakeRange(nOC,1)] isEqualToString:#"\n"]; ) {
numberOfLines++;
}
nOC++;
}
NSString *finalString = [str substringWithRange:NSMakeRange(1,nOC)];
i hope this should work out for u. i calculated the number of characters till the 4th "\n" and used substringWithRange to extract the desired string. i didnt try this piece of code but this logic should work or at least assist u in your code. happy coding :)

Mimic the iPhones phone keypad input

How would you go about mimicing the iPhones keypad input. So that when you click one 1 is displayed then 2 then it is 12... so on and so forth along with the ( ) -. I don't want to use the actual phone app because I'm creating a false dialer, but I want it to look and function kind of like the actual thing.
Any ideas would be greatly appreciated thanks.
EDIT:
Ok so I put in all the buttons needed but I ended up making them all individual buttons. THey are all linked including the label and this is what one button and the updater looks like.
-(IBAction)zeroButton:(id)sender{
self.enteredPhoneNumberString = [self.enteredPhoneNumberString stringByAppendingString:[NSString stringWithFormat:#"%d", "0"]];
[self updateFormattedPhoneNumberLabel];
}
-(void)updateFormattedPhoneNumberLabel {
if ([self.self.enteredPhoneNumberString length] > 3) {
NSString *firstThree = [self.enteredPhoneNumberString substringToIndex:2];
NSString *lastSet = [self.enteredPhoneNumberString substringFromIndex:2];
self.label.text = [NSString stringWithFormat:#"%#-%#", firstThree, lastSet];
}
else if ([self.self.enteredPhoneNumberString length] > 7) {
NSString *firstThree = [self.enteredPhoneNumberString substringToIndex:2];
NSString *secondThree = [self.enteredPhoneNumberString substringToIndex:2];
NSString *lastSet = [self.enteredPhoneNumberString substringFromIndex:2];
self.label.text = [NSString stringWithFormat:#"(%#) %#-%#", firstThree, secondThree, lastSet];
}
}
I had also tried it with the "" not being around the numbers being appended. Any idea why nothing is being displayed?
NEW EDIT:
I added enteredPhoneNumberString = #""; and with the numbers formatted the way you original had it displays the numbers. The main issue I'm having now is getting it so that the () and - pop up in the right spots.
I would suggest creating a grid of UIButtons that mimics the numpad, these buttons all call a method such as keyPadButtonTouchedUpInside:(id)sender and have a tag that corresponds to the number it represents.
Implementation of keyPadButtonTouchedUpInside:(id)sender may look like...
- (void)keyPadButtonTouchedUpInside:(id)sender {
UIButton *touchedButton = (UIButton *)sender;
if (touchedButton.tag <= 9) {
self.enteredPhoneNumberString = [self.enteredPhoneNumberString stringByAppendingString:[NSString stringWithFormat:#"%d", touchedButton.tag]];
[self updateFormattedPhoneNumberLabel];
} else {
// maybe some other code for pounds/stars entered on the keypad if you have these
// you will also be checking if the user hit the backspace key and trim your
// phone number string by 1
}
}
Now you need to implement updateFormattedPhoneNumberLabel
This will look at the instance NSString variable self.enteredPhoneNumberString and update a UILabel that you have in place to display the number.
updateFormattedPhoneNumberLabel might look like...
- (void)updateFormattedPhoneNumberLabel {
if ([self.self.enteredPhoneNumberString length] > 3) {
NSString *firstThree = [self.enteredPhoneNumberString subStringToIndex:2];
NSString *lastSet = [self.enteredPhoneNumberString subStringFromIndex:2];
self.formattedPhoneNumberLabel.text = [NSString stringWithFormat:#"%#-%#", firstThree, lastSet];
} else if ....
// more conditions to check for other lengths of the
// entered number and continue with the proposed formatting methods.
Hopefully that gets you down the path, there may be more efficient methods for doing this but in reality its not an intensive operation so I wouldn't worry to much about optimization unless you see some kind of entry lag which I wouldn't expect.
EDIT
I would probably update the formatting conditions so that the formatting happens in the following behavior.
1-3 numbers entered shows as "1", "12", or "123"
4-7 numbers entered shows as "123-4", "123-45", "123-456", or "123-4567"
8-10 numbers entered show as "(123) 456-78", "(123) 456-789", or "(123) 456-7890"
11 numbers entered show as "1+(234)-567-8901"
Anything more than 11 I would just show a string of numbers, unless you want to get into formatting non-us numbers. You should also play around with entering numbers in the Phone App to see how it responds if you want to mimic it completely.

Couple Obj C issues including an error

So basically in this code I placed below there is a couple things I would like to accomplish. 1) In that first one I would like the Text Field to close after done is pressed, but it is not doing so. (Found that code on other forums). 2)On that first button when it is pressed down I would like it to send a time to be saved for when the button is released. 3) On release of that button I would like it to calculate the time between the two times along with some other calculations later.
Problem that I am mainly getting here is the error from the NSTimeInterval. It keeps telling me that NSTimeInterval is incompatiable with type NSTimeInterval. So a double is incompatible with a double??? (I have also tried it with NSTimeInterval *timePassed in the .h and just trying to set timePassed = to it and it doesn't work either. Similar error happens.
#import "MphViewController.h"
#implementation MphViewController
#synthesize speed, distance;
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
[textField resignFirstResponder];
return YES;
}
-(IBAction)triggerDown:(id)sender{
timeStart = [NSDate date];
}
-(IBAction)triggerUp:(id)sender{
NSInteger *dist;
NSString *display;
NSTimeInterval *timePassed = [timeStart timeIntervalSinceNow];
if ([distance.text length]== 0) {
display = #"Please enter a distance";
}
else{
dist = atoi(distance.text);
display = [[NSString alloc] initWithFormat:#"%d MPH",dist];
}
speed.text = display;
[display release];
}
Also if you have a chance to glance at the else statement. I'm not sure if that will work (seeing is after I enter a distance I still cant get rid of the numberpad). Id like to pass mph once it is calculated into that to be displayed on the screen. Very confused with how parsing is done on obj c. Anyways thanks for any and all help.
As for the textField part. My textField that it is assigned to is distance. Should that Bool be written differently to have distance in it or is that just the way it is supposed to be written?
You are denoting primitive types as pointers where they do not need to be. Remove the star (*) from the NSInteger and NSTimerIntervaldeclaration. The if check looks fine but instead of atoi just use:
dist = [distance.text integerValue];
Edit:
Notice a couple of memory management issues.
1 . Make sure you properly retain/release the date then later release it in dealloc.
-(IBAction)triggerDown:(id)sender{
[timeStart release];
timeStart = [[NSDate alloc] init];
}
2 . Your if statement will result in trying to release an NSString literal which likely will not crash but is incorrect memory management. Just use an autoreleased string and remove the [display release];
...
else{
dist = [distance.text integerValue];
display = [NSString stringWithFormat:#"%d MPH",dist];
}
speed.text = display;
}

How do I do decimal formatting in Objective-C?

Pretty new to the whole iPhone development scene. I am just practicing, trying to create a basic calculator, I can add simple numbers but I'd like to support decimal places.
Heres my code so far:
- (IBAction) calculate
{
double number1 = ([textField.text doubleValue]);
double answer = number1+([textField2.text doubleValue]);
label.text = [[NSString alloc] initWithFormat:#"%2.f", answer];
}
- (IBAction) clear
{
textField.text = #"";
textField2.text = #"";
label.text = #"";
}
Any help much appreciated.
I think your format might be wrong. What is the output you're expecting, and what are you getting?
If I'm guessing correctly, you may want to try this:
label.text = [NSString stringWithFormat:#"%5.2f", answer];
where the 5 means total digits (in terms of padding for alignment), and the 2 means 2 decimal places.
EDIT: avoiding memory leak, as mentioned in donkim's comment!

How to know the displayed text in UILabel?

I have an UIView containing two UILabels, in order to display a string.
The first UILabel has a fixed size, and if the string is too long and can't hold in this UILabel, I want to display the maximum characters I can in the first UILabel, and display the rest of the string in the second UILabel.
But to make this, I must know the exact part of the string displayed in the first UILabel, which is not easy because of the randomness of the string and the linebreaks.
So, is there a way to get just the text displayed in the first UILabel, without the truncated part of the string?
if ([_infoMedia.description length] > 270) {
NSRange labelLimit = [_infoMedia.description rangeOfString:#" " options:NSCaseInsensitiveSearch range:NSMakeRange(270, (_infoMedia.description.length - 270))];
_descTop.text = [_infoMedia.description substringToIndex:labelLimit.location];
_descBottom.text = [_infoMedia.description substringFromIndex:(labelLimit.location+1)];
} else {
_descTop.text = _infoMedia.description;
_descBottom.text = #"";
}
Okay that's a late answer but maybe it could help someone. The code above is approximatively the solution I used in my app.
_descTop is my first label and _descBottom is the second label. 270 is a constant equivalent to a little less than the average maximum number of characters displayed in my first label, _descTop. I calculated it by hand, trying with many different strings, maybe there's a better way to do that but this worked not bad.
If the string I want to display (_infoMedia.description) is larger than 270 characters, I isolate the first 270 characters plus the end of the next word in the string (by searching the next space), in the case where the 270 characters limit would cut the string in the middle of a word. Then I put the first part of the string in my first label, and the second part in the second label.
If not, I only put the globality of the string in the first label.
I know that's a crappy solution, but it worked and I didn't found any better way to do that.
Following code might help you in getting what you want!!
//If you want the string displayed in any given rect, use the following code..
#implementation NSString (displayedString)
//font- font of the text to be displayed
//size - Size in which we are displaying the text
-(NSString *) displayedString:(CGSize)size font:(UIFont *)font
{
NSString *written = #"";
int i = 0;
int currentWidth = 0;
NSString *nextSetOfString = #"";
while (1)
{
NSRange range;
range.location = i;
range.length = 1;
NSString *nextChar = [self substringWithRange:range];
nextSetOfString = [nextSetOfString stringByAppendingString:nextChar];
CGSize requiredSize = [nextSetOfString sizeWithFont:font constrainedToSize:CGSizeMake(NSIntegerMax, NSIntegerMax)];
currentWidth = requiredSize.width;
if(size.width >= currentWidth && size.height >= requiredSize.height)
{
written = [written stringByAppendingString:nextChar];
}
else
{
break;
}
i++;
}
return written;
}
#end