Enable button only after text fields are entered - objective-c

Apple started rejecting my app (after yrs of approvals) - I have 2 text fields for a few char of last, first name, then press Go. They claim - I can't reproduce that the app crashes if you just press Go - when I run it it tries to send the request but kicks it back with an error 'No name - please reenter'. Whatever. To appease Apple I edited my text field entry method to now (see below) - but still not working. Please advise.
// Text Field methods
-(void)textFieldDidBeginEditing:(UITextField *)textField {
UIFont* boldFont = [UIFont fontWithName:#"Helvetica-Bold" size: 24];
if (textField == _lastName) {
[_lastName becomeFirstResponder];
[_lastName setFont:boldFont];
_lastName.autocorrectionType = UITextAutocorrectionTypeNo;;
_lastName.autocapitalizationType = UITextAutocapitalizationTypeNone;
_lastName.text = #"";
NSLog(#"user editing last...");
// Enable button once fields have values
if ([_lastName.text isEqual: #""] && [_firstName.text isEqual: #""])
{
_fwdButton.enabled = YES;
}
}
else {
[_firstName becomeFirstResponder];
[_firstName setFont:boldFont];
_firstName.autocorrectionType = UITextAutocorrectionTypeNo;;
_firstName.autocapitalizationType = UITextAutocapitalizationTypeNone;
_firstName.text = #"";
NSLog(#"user editing first");
// Enable button once fields have values
if ([_lastName.text isEqual: #""] && [_firstName.text isEqual: #""])
{
_fwdButton.enabled = YES;
}
}
}

I was able to add code similar to[https://stackoverflow.com/questions/2859821/disable-button-until-text-fields-have-been-entered][1]
to deal with the situation.

Related

Saving data entered in textfield of custom cells

I have a custom cell.In that custom cell,there is a textfield and a label.I actually have made a form using custom cell.The user will enter its name,city,state,country,dob detail in that textfield.Now ,on a click of a button I want to save all this data together in a dictionary.But I am not able to understand that how can I save data for different keys using the same textfield as it is being reused.Please help with some code in objective c.Thanks in advance!
I am giving you idea, so that you can implement it
Just take a Macro, like this #define textFieldTag 1000
You are re-using the textfField right. So, in cellForRowAtIndexPath (if u r using table view) set the tag of the textField like this: cell.textType.tag = indexPath.row + textFieldTag;
then
access the textField using delegate
- (void)textFieldDidEndEditing:(UITextField *)textField {
switch (textField.tag - textFieldTag) {
case 0:{
NSString *name = textField.text;
//Take a NSMutableDictionary and add the values over here
break;
}
case 1:
{
NSString *city = textField.text;
break;
}
case 2:
{
NSString *state = textField.text;
break;
}
case 3:
{
NSString *country = textField.text;
break;
}
case 4:
{
NSString *dob = textField.text;
break;
}
default:
break;
}
}
And one thing, when you click the save or what ever button u r using to submit the form remember to dismiss the keyboard [textField resignFirstResponder];, otherwise the last value can be nil.
Thank you

disabling rows on condition on xlform

I have a section set up using xlform where there is a bool field and then a text field under it. I want to disable the text field if the bool is chosen. I tried with the following, but it did not work
if ([[self.form valueForKey:#"pay"] isEqualToValue:#(1)]){
row.disabled = YES;
} else {
row.required = YES;
}
[section addFormRow:row];
Any suggestions? Here is the documentation, spent a bunch of time search without being able to find the answer.
Edit: I'm starting to think that the values of the dictionary don't get updated dynamically. Which is odd because the dictionary can be accessed in other parts of the view controller at any time.
Edit
- (void)formRowDescriptorValueHasChangedTwo:(XLFormRowDescriptor *)formRow value:(id)value
{
[value isEqual:[self.formValues valueForKey:#"pay"]];
if([formRow.tag isEqualToString:#"pay"] && [value isEqual:[NSNumber numberWithBool:YES]])
{
self.price.disabled = NO;
self.price.required = YES;
[self.tableView reloadRowsAtIndexPaths:#[[self.form indexPathOfFormRow:self.price]]
withRowAnimation:UITableViewRowAnimationNone];
}
}
I got it working, but I have a new problem.
Here is the code
- (void)formRowDescriptorValueHasChanged:(XLFormRowDescriptor *)formRow oldValue:(id)oldValue newValue:(id)newValue
{
if([formRow.tag isEqualToString:#"now"] && [newValue isEqual:[NSNumber numberWithBool:YES]])
{
self.time.disabled = YES;
[self.tableView reloadRowsAtIndexPaths:#[[self.form indexPathOfFormRow:self.time]]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
if([formRow.tag isEqualToString:#"pay"] && [newValue isEqual:[NSNumber numberWithBool:YES]])
{
self.price.disabled = NO;
self.price.required = YES;
[self.tableView reloadRowsAtIndexPaths:#[[self.form indexPathOfFormRow:self.price]]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
When I click on the first row (now), it disables the corresponding row fine, but when I click on the second row (pay), it makes the row that now disables reappear AND it makes the row that I want to appear under pay to disappear.
Edit Got it to work by changing the animation to UITableViewRowAnimationNone
Here's how you can do it using XLFormDescriptorDelegate. This will change the state as the field is toggled on the form.
#interface SomeClass() <XLFormDescriptorDelegate>
#end
#implementation SomeClass
- (void)formRowDescriptorValueHasChanged:(XLFormRowDescriptor *)formRow oldValue:(id)oldValue newValue:(id)newValue
{
if([formRow.tag isEqualToString:#"boolFieldTag"] && [newValue boolValue])
{
self.otherRow.disabled = YES;
[self.tableView reloadRowsAtIndexPaths:#[[self.form indexPathOfFormRow:self.otherRow]]
withRowAnimation:UITableViewRowAnimationNone];
}
}
#end
In this example I set the row as a property when I originally added it to the form. Alternatively you can just use: [self.form formRowWithTag:#"someTag"]

What is a convenient way to check UITextFields for errors before allowing a form to submit?

I've been at this all day and just can't get my head around it. On a form in my app, I'm using -[UITextFieldDelegate textFieldDidEndEditing]: to register any errors and store them in an NSMutableArray instance variable formErrors.
I intend to use the formErrors when my submit button is pressed, or perhaps to disable the button disabled while there are errors on the form. The problem is the error count goes all over the place. I've just ended up confusing myself as you can see my code where I'm incrementing and decrementing in order to try and control what's going on but just confusing myself more.
Error messages get put on formErrors like this:
-(void)textFieldDidEndEditing:(UITextField *)textField {
if ( textField == [self nameField] ) {
if ( ([[textField text] isEqualToString:#""]) ) {
[formErrors addObject:#"What is your name?"];
errorCount++;
} else {
errorCount--;
if ( ([[textField text] length] < 2) || ([[textField text] length] > 20) ) {
[formErrors addObject:#"Name must contain a minimum of 2 and a maximum of 20 characters only."];
errorCount++;
} else {
errorCount--;
if ([[textField text] rangeOfCharacterFromSet:alphaSet].location != NSNotFound) {
[formErrors addObject:#"Name must contain letters and spaces only."];
errorCount++;
}
}
}
}
if (textField == [self ageField]) {
if ( ([[textField text] isEqualToString:#""]) ) {
[formErrors addObject:#"How old are you?"];
errorCount++;
} else {
errorCount--;
if ( ([[textField text] intValue]) < 1 || ([[textField text] intValue] > 120) ) {
[formErrors addObject:#"Please enter an age using a number between 1 and 120."];
errorCount++;
} else {
errorCount--;
if ([[textField text] rangeOfCharacterFromSet:numericSet].location != NSNotFound) {
[formErrors addObject:#"Age must be given in numbers."];
errorCount++;
}
}
}
}
My instance var:
{
NSMutableArray *formErrors;
}
Then initialise it in viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
formErrors = [[NSMutableArray alloc] init];
Then in prepareForSegue: I have some temporary code to check things are working:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
int errCount = [formErrors count];
// check if all textfield values are filled in if not then disallow form submit
for (NSString *error in formErrors) {
NSLog(#"Total %d errors, \n Error Message: %#", errCount, error);
}
All I want to do is, as I enter and leave fields, check if there are any errors; if there are, just store the error message in formErrors, so I can do what I need to do in the prepareForSegue:. Is this even the right approach? I've tried doing this many different ways but just keep on going in circles.
The submit button is linked to my segue and also is an outlet so I can enabled and disable it as I please.
Help would be appreciated
Kind regards
Your approach is a bit redundant. Validate your fields upon submission, cancel submission if there are any errors:
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
NSMutableArray *errors = [NSMutableArray array];
if (self.nameTextField.text.length > 21) {
[errors addObject:#"Name cannot be longer than 21 symbols"];
}
else if (!self.nameTextField.text.length) {
[errors addObject:#"Please enter your name"];
}
if (!self.passwordTextField.text.length) {
[errors addObject:#"Please enter password"];
}
else if (!self.confirmPasswordTextField.text.length) {
[errors addObject:#"Please confirm your password"];
}
else if (self.passwordTextField.text.length < 6) {
[errors addObject:#"Password is too short, use at least 6 characters."];
}
else if (![self.passwordTextField.text isEqualToString:self.confirmPasswordTextField.text]) {
[errors addObject:#"Passwords do not match"];
}
if (!self.emailTextField.text.length) {
[errors addObject:#"Please enter your e-mail"];
}
if (!self.image) {
[errors addObject:#"Please choose a photo"];
}
if (errors.count) {
[[[UIAlertView alloc] initWithTitle:#"Error"
message:[errors componentsJoinedByString:#"\n"]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil, nil] show];
return NO;
}
return YES;
}
You're on the right path, but here are a couple of observations.
First I would get rid of all the errorCount++ and errorCount-- lines. They don't make sense, because you can always count the items in your error array.
Every time you check your input fields, clear your error array, otherwise you will be having errors that might have been already corrected.
Inside your -(void)textFieldDidEndEditing:(UITextField *)textField method, don't only check the textfield that has been edited, because you wold need to keep track of the errors that were before, the ones that were corrected, and the new ones... too much trouble.
I think it's best to create a new method that checks all the information and returns an array of errors. Call this routine from textFieldDidEndEditing.
- (NSArray *)checkInputs
{
NSMutableArray *errors = [NSMutableArray array];
// Loop through the textfields and fill your array
return (NSArray *)errors;
}

iOS 6 UITextField Secure - How to detect backspace clearing all characters?

In iOS 6 if you type text into a secure text field, change to another text field, then come back to the secure text field and hit backspace, all of the characters are removed. I am fine with this happening, however, I am trying to enable/disable a button based on if this secure text field has characters in it or not. I know how to determine what characters are in the fields and if a backspace is hit but I am having trouble determining how to detect if clearing of all the characters is happening.
This is the delegate method I'm using to get the new text of a field, but, I can't seem to figure out how to get the new text (assuming the new text would just be a blank string) if a backspace is hit that clears all the characters.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
//returns the "new text" of the field
NSString * text = [textField.text stringByReplacingCharactersInRange:range withString:string];
}
Any help is much appreciated.
Thanks!
I use this solution. It does not need local variables and sets the cursor position correctly, after deleting the char.
It's a mashup of this solutions:
Backspace functionality in iOS 6 & iOS 5 for UITextfield with 'secure' attribute
how to move cursor in UITextField after setting its value
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (range.location > 0 && range.length == 1 && string.length == 0)
{
// Stores cursor position
UITextPosition *beginning = textField.beginningOfDocument;
UITextPosition *start = [textField positionFromPosition:beginning offset:range.location];
NSInteger cursorOffset = [textField offsetFromPosition:beginning toPosition:start] + string.length;
// Save the current text, in case iOS deletes the whole text
NSString *text = textField.text;
// Trigger deletion
[textField deleteBackward];
// iOS deleted the entire string
if (textField.text.length != text.length - 1)
{
textField.text = [text stringByReplacingCharactersInRange:range withString:string];
// Update cursor position
UITextPosition *newCursorPosition = [textField positionFromPosition:textField.beginningOfDocument offset:cursorOffset];
UITextRange *newSelectedRange = [textField textRangeFromPosition:newCursorPosition toPosition:newCursorPosition];
[textField setSelectedTextRange:newSelectedRange];
}
return NO;
}
return YES;
}
Finally figured it out for anyone looking to see how to determine when a backspace is going to clear all the characters of a secure UITextField:
UITextField:
self.passwordTextField
Private property (initialized to NO in init - probably not needed):
self.passwordFirstCharacterAfterDidBeginEditing
UITextFieldDelegate Methods:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
//if text is blank when first editing, then first delete is just a single space delete
if([textField.text length] == 0 && self.passwordFirstCharacterAfterDidBeginEditing)
self.passwordFirstCharacterAfterDidBeginEditing = NO;
//if text is present when first editing, the first delete will result in clearing the entire password, even after typing text
if([textField.text length] > 0 && self.passwordFirstCharacterAfterDidBeginEditing && [string length] == 0 && textField == self.passwordTextField)
{
NSLog(#"Deleting all characters");
self.passwordFirstCharacterAfterDidBeginEditing = NO;
}
return YES;
}
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
if(textField == self.passwordTextField)
{
self.passwordFirstCharacterAfterDidBeginEditing = YES;
}
}
I hope this helps someone and I also hope Apple just creates a delegate method that is called when a secure text field is cleared by a delete - this seems a big cumbersome, but it works.
Secure text fields clear on begin editing on iOS6+, regardless of whether you're deleting or entering text. Checking if the length of the replacement string is 0 works if the text field is cleared from a backspace, but annoyingly, the range and replacement string parameters to textField:shouldChangeCharactersInRange:replacementString: are incorrect if the text is cleared when entering text. This was my solution:
1) Register for textFieldTextDidChange notifications.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(textFieldTextDidChange:)
name:UITextFieldTextDidChangeNotification
object:nil];
2) Re-invoke the textField:shouldChangeCharactersInRange:replacementString: delegate method if the text field is secure and may have been cleared.
- (void)textFieldTextDidChange:(NSNotification *)notification
{
if (textField.secureTextEntry && textField.text.length <= 1) {
[self.textField.delegate textField:self.textField
shouldChangeCharactersInRange:NSMakeRange(0, textField.text.length)
replacementString:textField.text];
}
}
The accepted solution does not enable a button. It actually changes backspace behaviour on a secure textfield.
This Swift solution does solve the question by correctly informing a delegate about the actual string that will be set in the textField, including when pressing backspace. A decision can then be made by that delegate.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
var shouldChange = true
if let text = textField.text {
let oldText = text.copy()
var resultString = (text as NSString).stringByReplacingCharactersInRange(range, withString: string) as NSString
let inputString = string as NSString
if range.location > 0 && range.length == 1 && inputString.length == 0 {//Backspace pressed
textField.deleteBackward()
shouldChange = false
if let updatedText = textField.text as NSString? {
if updatedText.length != oldText.length - 1 {
resultString = ""
}
}
}
self.delegate?.willChangeTextForCell(self, string: resultString as String)
}
return shouldChange
}
I was facing the same issue, I wanted to detect when the backspace is going to clear all characters so that I can enabled disable some other buttons on screen. So this is how I achieved it.
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
_checkForDelete = NO;
if (textField.isSecureTextEntry && textField.text.length > 0) {
_checkForDelete = YES;
}
return YES;
}
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (_checkForDelete && string.length == 0) {
_checkForDelete = NO;
//Do whatever you want to do in this case,
//because all the text is going to be cleared.
}
return YES
}
This solution is different because it lets you take action when a secure field already has some text in it and you are trying to edit it, rather in the above accepted answer, you will go in the block even if you hit backspace while already editing a field, that will not clear the whole text but only deletes one character. Using my method you have an option to take an action in this special case, but it will not impact the native flow either.
Posting my alternate solution as I had the exact problem as OP and found I did not have to do any cursor position alteration detailed in the chosen answer.
Below is the UITextFieldDelegate method, I call a custom delegate method didChangeValueForTextField as my button I want to enable is outside of this class.
Class implementing UITextFieldDelegate
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString *newValue = [textField.text stringByReplacingCharactersInRange:range withString:string];
BOOL shouldReturn = YES;
if (range.location > 0 && range.length == 1 && string.length == 0){
[textField deleteBackward];
if ([textField.text isEmptyCheck]) { // Check if textField is empty
newValue = #"";
}
shouldReturn = NO;
}
if(self.delegate && [self.delegate respondsToSelector:#selector(didChangeValueForField:withNewValue:)]) {
[self.delegate didChangeValueForField:textField withNewValue:newValue];
}
return shouldReturn;
}
The key was detecting the difference between secure field single character deletion and secure field single character deletion that triggers field clear. Detecting this within the above delegate method was necessary.
Class containing button to enable
- (void)didChangeValueForField:(UITextField *)textField withNewValue:(NSString *)value {
// Update values used to validate/invalidate the button.
// I updated a separate model here.
// Enable button based on validity
BOOL isEnabled = [self allFieldsValid]; // Check all field values that contribute to the button being enabled.
self.myButton.enabled = isEnabled;
}
swift3.0 - it works.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == passwordTextfield {
if range.location > 0 && range.length == 1 && string.characters.count == 0 {
if let textString = textField.text {
// iOS is trying to delete the entire string
let index = textString.index(textString.endIndex, offsetBy: -1)
textField.text = textString.substring(to: index)
return false
}
}else if range.location == 0 {
return true
}else {
if let textString = textField.text {
textField.text = textString + string
return false
}
}
}
return true
}

Anyway to make a (wrapping) NSTextField write a carriage return upon pressing return key?

I want to use a wrapping text field that can potentially contain carriage returns in my app. Is there any way to force the NSTextField object to write a carriage return into the text area instead of sending its action to its target when the Return key is pressed?
This is covered in Technical Q&A QA1454, which also enumerates reasons why one would use NSTextField instead of NSTextView in this case.
You can implement the following method in the text field delegate:
- (BOOL)control:(NSControl*)control
textView:(NSTextView*)textView
doCommandBySelector:(SEL)commandSelector
{
BOOL result = NO;
if (commandSelector == #selector(insertNewline:))
{
// new line action:
// always insert a line-break character and don’t cause the receiver
// to end editing
[textView insertNewlineIgnoringFieldEditor:self];
result = YES;
}
return result;
}
Okay, I figured out one way to do it, but this very well may not be the best (or even a good) way. I subclassed NSTextField, and overrode -textShouldEndEditing: like so:
-(BOOL)textShouldEndEditing:(NSText *)textObject {
NSEvent * event = [[NSApplication sharedApplication] currentEvent];
if ([event type] == NSKeyDown && [event keyCode] == 36) {
[self setStringValue:[[self stringValue] stringByAppendingString:#"\n"]];
return NO;
}
else {
return [super textShouldEndEditing:textObject];
}
}
I found a combination of Sean and Bevarious worked best for me. Sean's answer assumes that the new line is always wanted to be added to the end (instead of for instance where the user's cursor is placed).
-(BOOL)textShouldEndEditing:(NSText *)textObject
{
NSEvent * event = [[NSApplication sharedApplication] currentEvent];
if ([event type] == NSKeyDown && [event keyCode] == 36)
{
[textObject insertNewlineIgnoringFieldEditor:nil];
return NO;
}
else
{
return [super textShouldEndEditing:textObject];
}
}
Swift version:
override func textShouldEndEditing(textObject: NSText) -> Bool {
let event = NSApplication.sharedApplication().currentEvent
if event?.type == NSEventType.KeyDown && event?.keyCode == 36 {
self.stringValue = self.stringValue.stringByAppendingString("\n")
return false
} else {
return super.textShouldEndEditing(textObject)
}
}