Custom UITextField with max decimal numbers - ios

I would like to create a custom UITextField with:
1) Only one decimal point allowed.
2) A max number of decimals the user can enter (will be a property), let's say 2.
What would be my best to accomplish this?
I created a new class that inherits from UITextField.
For the point #1 I found on stackoverflow I should rely on the following method:
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
Implementing it like this:
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
NSString *newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSArray *sep = [newString componentsSeparatedByString:#"."];
if([sep count]>=2)
{
NSString *sepStr=[NSString stringWithFormat:#"%#",[sep objectAtIndex:1]];
return !([sepStr length]>1);
}
return YES;
}
The first problem is that this is a method of the UITextField delegate, not available for my new object that inherits from UITextField. I would like my custom UITextField to implement it by default but
there is not such method.
The second problem is that the decimal separator could be "." or ",". Is there a way to find what is the decimal separator for the pad?
For the point #2, the max number of decimas a user can enter, I guess I'd check if there are already 2 digits after the decimal separator, if so I won't modify the UItextField.text.
Thanks
Nicola

Implement the UITextFieldDelegate methods in your custom UITextField as normal.
Then you can create a proxy for the delegate that refers outside callers to the delegate as set by the ViewController
override the #property id<UITextfieldDelegate> to return the delegate set by the ViewController.
As implemented in your UITextField subclass:
(id) init {
self = [super init];
if (self) {
//register ourselves as the delegate with UITextField
super.delegate = self;
//custom initialization code
}
return self;
}
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
NSString *newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSArray *sep = [newString componentsSeparatedByString:#"."];
if([sep count]>=2)
{
NSString *sepStr=[NSString stringWithFormat:#"%#",[sep objectAtIndex:1]];
BOOL ret = !([sepStr length]>1);
if(ret)
return [_delegate textfield:textField shouldChangeCharactersInRange:range replacementString:string];
return return NO;
}
return [_delegate textfield:textField shouldChangeCharactersInRange:range replacementString:string];
}
Basically, make sure you forward the delegate methods to the ViewController if they should have a say in it. (For shouldChangeCharactersInRange:, the ViewController delegate only gets a say if it's okay to change the text )
protip: when you forward the delegate methods, make sure they implement them by checking with respondsToSelector:, and make sure that the delegate is non nil.

Related

How to configure a decimal numeric field for an iOS (iPhone) app?

I have an app that needs to include a field where the user can input a decimal scalar number. Is there a recipe for how to accomplish this? I've read the relevant parts in Matt Neuburg's "Programming iOS 7" book. I've tried a Cocoapod (MPNumericTextField). I'm not having a lot of luck...
What I've accomplished so far:
Painted a UITextField in my storyboard.
Given it an outlet and a delegate.
Set its Keyboard Type to Decimal Pad.
Implemented the following 3 methods:
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField {
NSLog(#"textFieldShouldEndEditing:");
[textField resignFirstResponder];
return YES;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
NSLog(#"textFieldShouldReturn:");
[textField resignFirstResponder];
return YES;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField {
NSLog(#"textFieldDidBeginEditing:");
[textField selectAll: nil];
}
It appears that I'll have to implement textField:shouldChangeCharactersInRange:replacementString: method to avoid duplicate decimal points being put in.
But I can't figure out how to dismiss/close the keyboard when I'm done inputting the number. What's the point of the Decimal Pad if you can't close it?
Are there other things I have to do get a simple numeric field working?
Following line of code might serve your need
[self.view endEditing:YES];
You will need to implement delegate like this to scan number and avoid double . in value.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (textField == _amountField) {
NSString *newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
if ([newString length] == 0)
{
return YES;
}
double holder;
NSScanner *scan = nil;
scan = [NSScanner scannerWithString: newString];
return ([scan scanDouble: &holder] && [scan isAtEnd]);
}
return YES;
}

Unit Testing UITextField Method

I'm very much new to iPhone app development, so starting with some unit testing. Pardon me if it is trivial question.
I have following code for a text field,
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
//Check whether currently editing text field is a account name text field
if([[_customCellObjects objectAtIndex:0] textField] == textField) {
NSUInteger newLength = [[[_customCellObjects objectAtIndex:0] textField].text length] + [string length] - range.length;
//Return NO(Non Editable) if the account name text field exceeds more than 50 characters
return (newLength > 50) ? NO : YES;
}
else {
//Return YES(Editable) for the other text fields
return YES;
}
}
How to invoke the above method with XCTest? and how to pass parameters to it?
What I do for UITableViews is expose the table view as a property, then call the delegate/data source functions directly and check the desired output.
In your case you would create a textField property that returns [[_customCellObjects objectAtIndex:0] textField] Or expose _customCellObjects
YourViewController *vc = [[YourViewController alloc] init];
bool result = [vc textField:vc.textField shouldChageCharactersInRange:NSMakeRange(0,1) replacementString: "a"]l
XCTAssertTrue(result);
The only thing I don't like about this is exposing the UITextField (or UITableView) unnecessarily. But others may post better options.

Update UINavigation Title as I Type in the UITextField

I have a UITextField and I want that when I type in the UITextField it should update the NavigationBar title as I type.
I have the following code but this does not work well.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
self.navigationItem.title = textField.text;
return YES;
}
Any recommendations?
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
self.navigationItem.title = newString;
return YES;
}
Otherwise the string (textField.text) is the string before the new update is made.
Hope this helps :)
Let me know if anything is not clear!
NSRange is defined:
typedef struct _NSRange {
NSUInteger location;
NSUInteger length;
} NSRange;
It doesn't do a lot, it's just quite convenient.
In terms of this method it just tells you which part of the textFields text will be replaced with the new string. When a user is typing on the end of the textField, the location will be the length of the text in the textField and the length will be 0. So no text will be replaced, it will just be inserted at the end... Make sense?

UITextFieldDelegate, checking if textFields have text in it

i have a little problem.
There are two textFields in my TableView which set the property of an object. In order to do so i want to force the user to write something in the textField before the string is actually been set to the object. So basically a simple ([textField.text length] > 0) thing.
But i want that the user have to write strings in both the two textFields to finally enable the "Done"-Button.
I solved this earlier but with only one text Field with the following UITextFieldDelegate method.
- (BOOL)textField:(UITextField *)theTextField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *newText = [theTextField.text stringByReplacingCharactersInRange:range withString:string];
self.doneBarButton.enabled = ([newText length] > 0);
return YES;
}
My solution for the new problem, so now with two textFields is this one:
- (BOOL)textField:(UITextField *)theTextField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *newText = [theTextField.text stringByReplacingCharactersInRange:range withString:string];
if ([theTextField.placeholder isEqualToString:#"textField1"]) {
if ([theTextField.text length] > 0) {
enabledVokabel = YES;
} else {
enabledVokabel = NO;
}
}
if ([theTextField.placeholder isEqualToString:#"textField2"]) {
if ([theTextField.text length] > 0) {
enabledUebersetung = YES;
} else {
enabledUebersetung = NO;
}
}
self.doneBarButton.enabled = (enabledVokabel && enabledUebersetung);
return YES;
}
So i want the doneBarButton been enabled when both of the textFields (textField1 and textField2) are filled with text. But i want it that way that if the user has deleted the text he/she just wrote in the doneBarButton is disabled as soon as the textFields are empty.
It doesn't work that way. Do you have a solution? Or maybe a better way to solve it?
Either just connect value changed in interfacebuilder to a IBAction method in any of the classes you have in your view. Or you can do it in code with:
[textField addTarget:self
action:#selector(myIBActionMethod:)
forControlEvents:UIControlEventEditingChanged];
And check the length of the input.
You can of hook up both textfields to the same method and check the length of both textfields every time its called if you have IBOutlets to them both.
I'd keep a reference for both UITextViews, lets say.-
IBOutlet UITextView *textView1;
IBOutlet UITextView *textView2;
properly linked to your xib/storyboards. Also, I'd rather use
- (void)textViewDidChange:(UITextView *)textView
callback. According to shouldChangeCharactersInRange docs, it looks like this method is called before actually changing the text.
As for the enabled condition, it would look something like this.-
self.doneBarButton.enabled = [textView1.text length] > 0 && [textView2.text length] > 0;

Set max length of UITextField in a UIAlertView

I want to set a max length to my UITexField but this is inserted in a UIAlertView,
I'm trying to find out the right information to solve this problem,on this beautiful site but there is nothing.
Could someone help me to this?
I think this is a better implementation of the UITextFieldDelegate method, as it will not remove the text end in front of the users eyes, it just prevents the user from typing in more text.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
if (newString.length > MAXLENGTH)
{
return NO;
}
return YES;
}
Don't forget to implement the UITextFieldDelegate protocol, and set your controller as the delegate of the text field (see Anoop Vaidya answer)
As you can access the alertView's textField by
UITextField *theTitleTextField = [alertView valueForKey:#"_bodyTextLabel"];
If you want to find your's textfield added, then use textFieldAtIndex: as suggested by Martin R.
Then implement UITextFieldDelegate protocol.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if ([textField.text length] > MAXLENGTH) {
textField.text = [textField.text substringToIndex:MAXLENGTH-1];
return NO;
}
return YES;
}

Resources