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.
Related
While trying to allow multi token deletions, as user holds the backspace key in VENTokenField to act the same as the native email app, or messages app, I have come across many problems...
First, I can only detect one tap on the backspace key from the initial code the VENToken's UITextField subclass offer (which is technically touching private API) - (BOOL)keyboardInputShouldDelete:(UITextField *)textField. That is fine, but not helpful for detecting long press on backspace button, which only works while you actually have characters in a certain UITextField, and not while the UITextField is empty such as in our case.
I have also came across this blogpost which suggest another approach of accessing more of the private API, however, does not offer solution to my problem. As it's not documented, I was wondering if there is a valid way to detect this event at all?
I've resolved it by first, comment out anything that was in VENBackspaceTextField class'
keyboardInputShouldDelete:(UITextField *)textField
Then, added 2 consts in VENTokenField header:
NSString * const kTextEmpty = #"\u200B"; // Zero-Width Space
NSString * const kTextHidden = #"\u200D"; // Zero-Width Joiner
Everytime the token becomes first responder, make sure the textField has the empty text:
- (void)inputTextFieldBecomeFirstResponder {
[self.inputTextField becomeFirstResponder];
if (self.tokens.count) {
[self.inputTextField setText:kTextEmpty];
}
...
}
And set it to hidden when cursor is not visible:
- (void)setCursorVisibility {
NSArray *highlightedTokens = [self.tokens filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(VENToken *evaluatedObject, NSDictionary *bindings) {
return evaluatedObject.highlighted;
}]];
BOOL visible = [highlightedTokens count] == 0;
if (visible) {
[self inputTextFieldBecomeFirstResponder];
} else {
[self.invisibleTextField becomeFirstResponder];
[self.invisibleTextField setText:kTextHidden];
}
}
Then, modified the textField Delegate method:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if (self.tokens.count && [string isEqualToString:#""] && [textField.text isEqualToString:kTextEmpty]){
VENToken *lastToken = [self.tokens lastObject];
lastToken.highlighted = YES;
[_inputTextField setText:kTextHidden];
_inputTextField.alpha = 0.0;
return NO;
}
if ([textField.text isEqualToString:kTextHidden]){
[self deleteHighlighted];
[self unhighlightAllTokens];
return (![string isEqualToString:#""]);
}
//If there are any highlighted tokens, delete
[self deleteHighlighted];
return YES;
}
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;
}
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;
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.
short version: How can I make a UITextField box remove all content on the users first keypress? I don't want the info removed until the user starts typing something. ie, clearing it on begin edit is not good enough.
long version: I have three UITextField that loop around (using the return key and catching the press in the "shouldReturn" method. There is text already in the UITextField, and if the user doesn't type anything and just goes to the next UITextField, the value should stay (default behaviour).
But I want it that if the user starts typing, it automatically clears the text first. Something like having the whole field highlighted, and then typing anything deletes the fiels and then adds the user keypress.
"Clear when editing begins" is no good, because the text is immediately cleared on the cursor appearing in the field. That's not desired. I thought I could use the placeholder here, but that doesn't work as a default, and I can't find a default value property. The Highlighted and Selected properties don't do anything in this regard either.
There is a delegate method called
textFieldDidBeginEditing:(UITextField*) tf{
tf.startedEdinting = YES;
}
textFeildDidEndEditing: (UITextField*) tf {
tf.startedEditing = NO;
}
Add startEditing in a category to UITextField.
Then if value changes clear the field:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if (textField.startEditing){
textField.text = string;
} else {
textField.text = [textField.text stringByReplacingCharactersInRange:range withString:string];
}
}
You can add the property to the UITextField category in the following way:
.h
#property (nonatomic, assign) BOOL startEditing;
.m
#dynamic startEditing;
- (void) setStartEditing:(BOOL)startEditing_in{
NSNumber* num = [NSNumber numberWithBool:startEditing_in];
objc_setAssociatedObject(self, myConstant, num, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL) startEditing{
NSNumber* num = objc_getAssociatedObject(self, myConstant);
return [num boolValue];
}
Declare a BOOL variable in your .h file like.
BOOL clearField;
And implement the delegate methods like:
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
clearField = YES;
}
-(void)textFieldDidEndEditing:(UITextField *)textField
{
clearField = NO;
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
clearField = NO;
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if(clearField)
{
textField.text = #""
clearField = NO;
}
}
I want to thank people for their answers, I implemented both of the main methods described here and both worked flawlessly. But I have since come across a much simpler, nicer answer and involves only one line of code :)
In the textField's didBeginEditing method, place [self.textField selectAll:self]; or [self.textField selectAll:nil];
The original answer I found had selectAll:self but this shows the cut/copy/paste menu. If you send nil instead of self the menu doesn't appear.
Adding this one line of code highlights the text on entering the textField (so gives the user a visual cue), and only removes everything once a key is pressed.
Another solution that fulfils the same purpose is by simply using a text field placeholder which is defined as:
The string that is displayed when there is no other text in the text field.
So as soon as the user starts typing, the placeholder text disappears.
That's something you can set from the storyboard, or programmatically. (Yes it took me two hours trying to figure it the harder way.. when the solution was literally one line change of code).
If you want to clear the text one the user interacts with it, there is an option in interface builder to where you can set the text field to "Clear when editing begins."
Try to use the following method.
- (BOOL) textField: (UITextField *)theTextField shouldChangeCharactersInRange: (NSRange)range replacementString: (NSString *)string {
if(isFirsttime==YES)
{
textfield.text==#"";
isFirsttime=NO;
}
return YES;
}
Declare and initialize a NSString variable for your textField's initial text
NSString *initialText=#"initial text";
Then implement methods:
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
if(textField.text isEqualToString:initialText)
{
textField.text=#"";
}
}
-(void)textFieldDidEndEditing:(UITextField *)textField
{
if(textField.text isEqualToString:#"")
{
textField.text=initialText;
}
}