Multiple Text Fields Invalid Entry Problem

REFERENCE SCREENSHOT:

h ttps://drive.google.com/file/d/1rQ9qZPNoFGCfEndGOEbYLfsIET7TYuEf/view?usp=sharing



Hi,


I am working on a simple form with 5 text fields and a UIButton, particualry the the titleLabel.text property of the that button. What I am trying to do is to peform a check when the user hits the SignUp Button (see link for screenshot). So when the signup button is tapped, I want to run a check to see if:


A. Any of the text fields are empty (thinking about using the isEmpty property)

B. If the Button Label text is still "Tap to choose a role..." If the user selected a role by tapping the button, this text would be replaced by the role which confirms that the user selected a role.


When the check is peformed, if any combination of empty textfields and/or button labels are "invalid" (i.e empty or incorrect label), then I want to display a UIAlertController and the message parameter of the alert would show the textfields and/or button combos that need to be filled.


Any guidance on how to begin would be great, there are so many combinations, that could be possible ( 1 text fields, 2 textfields + button, just the button, etc). Here is my current code, but I dont wanna go down this route since that would be a lot of code writing and its probably not the best solution but a start...


// IBAction function for when the user taps sign up button
@IBAction func whenSignUpButtonIsTapped(_ sender: UIButton) {


     //Workflow
     // 1. Validation check
     // 2. Register User
      
     // 1 validation check...
      if (firstNameTextField.text?.isEmpty)!  {
         
         
         let missingInformationAlert = UIAlertController(title: "Missing Information", 
                                                        message: "First Name is required", 
                                               preferredStyle: .alert)
                
         let cancelAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
         
         
         missingInformationAlert.addAction(cancelAction)

         self.present(missingInformationAlert, animated: true, completion: nil)
         
         
      }


Thanks!

Accepted Reply

Thanks PBK and Claude31 and everyone else for the help on validating text inputs and keypad help...learned a lot of from this discussion 🙂. I decided to just implement a UIToolbar and make it the inputAccessoryView on the keypad and it works like a charm. Thanks again! I dont know what answer to put as the correct one since there is no one correct answer. I'm just putting my reply as one.

Replies

create a single method that inspects text and returns a yes value if the text is acceptable. Then call this method 5 time’s passing the value in each texfield:


if([self testText:textfield1.text] && [self testText:textfield2.text] && ....){

// do thing 1

}else{

// do thing 2

}

At first glance, what I would do in such a case.


- define Bool var for each of the 6 items that need to be OK.

var firstNameOK = false
var roleOK = false

// etc…


- I suppose you have a property to keep track of selected role, which is updated when user taps or reset to nil

var selectedRole : Int?


These could be in an array, in a dictionary, but that does not matter a lot.


- have a func to test validity


func areEntriesOK() -> Bool {
     firstNameOK = firstNameTextField.text?.isEmpty ?? false
     roleOK = selectedRole != nil
     // idem for others
     return firstNameOK && roleOK && …………
}


- use it in whenSignUpButtonIsTapped

@IBAction func whenSignUpButtonIsTapped(_ sender: UIButton) {
     if areEntriesOK() {
          // proceed
     } else {
          // alert, and use the Bool var to tell what's wrong
     }
}

okay... So I wrote a quick playgound and this is where I am so far... I am tryinto map out my error strings that I want to display in my UIAlertController. Basically, only dispay the error strings that represent invalid text field entries.. Would I have to use a dictionary to map this out... or is there a simpler solution and I might be complicating this:


I'm starting out with PBKs version first and try out Claude31s as well... regardless.. this is where i am


Playground - Text Field Validation


//: Playground - Text Field Validation

import UIKit


// Constant Error Strings to display in UIAlertController
let firstNameError = "First Name Required"
let lastNameError = "Last Name Required"
let phoneNumberError = "Phone Number Required"
let emailError = "Email Required"
let passwordTextFieldError = "Password Required"
let buttonError = "Role Selection Required"


// Pretending these text values are coming from some text fields and UIButtons for now...
var firstNameTextFieldText = ""
var lastNameTextFieldText = "Earl"
var phoneTextFieldText = "555 555 5555"
var emailTextField = ""
var passwordTextField = "1234!"
var buttonTitleLableText = "Tap to Choose..."



// Method to check for texts
func testTextField(testString: String) -> Bool {
   
   
   if testString.isEmpty {
      
      return false
      
   } else {
      
      return true


}



}

testTextField(testString: lastNameTextFieldText) // I get a true :) Good sign!
testTextField(testString: firstNameTextFieldText) // I get a false :) Good sign!

Alerts are bad, 'mkay?


From a user experience perspective, it would be better to provide the user with feedback *before* he tries to tap a Submit button. Each required field should have an asterisk or highlight or "X" icon or something, which changes (turn red to green, X to check, remove asterisk, or whatever) once the user enters valid data. Ideally the user should be able to figure out what he has to do long before trying to tap the button (which, ideally, would be disabled until such time as tapping it will do something useful).


But back to your question. If you're set on checking at the end and using an alert, I'd go with the approach suggested by others, checking each text field in turn using a helper method. I would accumulate any error(s) in a mutable string. So if there were multiple problems with the data, they are all listed in one alert, instead of having to tap through 5 alerts, or fix one problem only to be presented with a different error alert the next time. Something like


var errorStr: String
if (!testTextField(testString: lastNameTextFieldText) {
    errorStr += lastNameError
}
if (!testTextField(testString: firstNameTextFieldText) {
    errorStr += firstNameError
}
// etc.


Of course you'd need line endings, punctuation or whatever makes sense. My syntax may be way off since I'm not a Swift guy and I just typed it in the browser. But the gist of it is to check all the fields and accumulate a nice error message in a mutable string as you go. Then present that in the alert if necessary. You can keep a separate boolean for whether any errors were detected in any of the individual checks, or just use the error message string being non-empty.

I like your suggestion... about providing feedback before tapping sign up. I'm gonna try that... would it still be a good idea to have the alert system in addition to pre-submission feedback? I've seen many scenarios:


1. Only pre submission feedback

2. Pre-submission + check at submit time

3. Only at submit time

Now that I am thinking about it, if I go with the Pre check and the button disabled until all the fields are complete that would the best... Gonna work on this. Thanks

Another way to do it:

- disable button if not OK

- add user information (in a label) why button is disabled

Okay, so I added an imageview next to each of my text fields in the table view cells to display and red asterisk or a green check mark. I also made a method that validates the text field and then updates the image views accordingly. I then call this validate method in viewDidLoad, however, if I type in the text field, the check mark doesn't appear. I think it has something to do with my logic or the fact that I placed it in the viewDidLoad method and its supposed to be somewhere else??? Here's the code:


  
/// Calling validate method here.. maybe this is the problem???
override func viewDidLoad() {
      
      
        super.viewDidLoad()
      

        validate(textField: firstNameTextField, validationIndicatorImageView: firstNameValidationIndicatorImageView)
      
    }
   
   
   
   
   // Validates a textfield to see if it is empty or not and updates an image view to either display an asterisk or checkmark
   // Paramaters
   //
   // textField: The text field to validate
   // validationIndicatorImageView: Image view for corresponding text field
   
   
   func validate(textField: UITextField, validationIndicatorImageView: UIImageView) {
      
      // If text field is empty, then the image view will display the asterisk glyph
      // otherwise, the image view will display a check mark glyph.
      if textField.text == "" {
         
         
         validationIndicatorImageView.image =  imageLiteral(resourceName: "Asterisk")
         
         
      } else {
         
         
         validationIndicatorImageView.image =  imageLiteral(resourceName: "Checkmark")
      
   }
   
   
   
   
   }

Thanks

validate should occur when you have entered something in the textField, not at viewDidload only.


So, call validate in the EditingDidEnd handler of the textField

- create a func editingEnded, which calls validate

- connect the Editing Did End event of the textField

- connect as well Did End On Exit to the same func

Okay. let me try this out.. thanks 🙂

You don’t want to wait for edit ended. You want to indicate a valid (or not yet valid) entry after each key is pressed. There is a delegate function that is called on each keystroke -use that.

Okay... so I made a few changes:

1. Moved the UIImageView to be inside the text field (instead of next to the textfield) on the right side using the rightView and rightViewMode properties. I was having trouble updating the images easily if they were outside the text field.


2. Made a UITextField extension to handle validations since I want to have different methods for validating email or password text fields in the future.


I tested out this code and called it in the textFieldShouldReturn and it seems to working... One issue that I don't like is that my implementation feels kinda "clunky", for example line 03. Ideally, I would like to just say textField.validateTextField.


func textFieldShouldReturn(_ textField: UITextField) -> Bool {
      textField.resignFirstResponder()
      textField.validateTextField(textField: textField)
      
      return true
   }



Here is the full code in Context


UITextFieldExtension

//UITextField Extension
import UIKit

extension UITextField {
   
   // Setup and update view for displaying asterisk or check mark.
   func updateView(indicatorImage: UIImage) {
      
      rightViewMode = .always
      let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 17, height: 17))
      imageView.image = indicatorImage
      rightView = imageView
      
      
   }
   
   // Takes in a UITextField and checks to see if its empty. If so, astersisk is dispalyed, else check is displayed
   func validateTextField(textField: UITextField) {
      
      
      if (textField.text?.isEmpty)! {
         
         updateView(indicatorImage:  imageLiteral(resourceName: "Asterisk"))
         
         
      } else {
      
         updateView(indicatorImage:  imageLiteral(resourceName: "Checkmark"))
      
   }
 }
}



ViewController (Only code related to text field is shown...

/// TEXT FIELD IB PROPERTIES HERE...
 @IBOutlet weak var firstNameTextField: UITextField!

override func viewDidLoad() {
      
      
        super.viewDidLoad()
      
    
      firstNameTextField.delegate = self
}
  
 func textFieldShouldReturn(_ textField: UITextField) -> Bool {
      textField.resignFirstResponder()
      textField.validateTextField(textField: textField) /// Can be improved??
      
      return true
  }
 }

Just write extension as :


extension UITextField { 
    
   func validateTextField() { 

      if self.text?.isEmpty)! { 
         updateView(indicatorImage:  imageLiteral(resourceName: "Asterisk")) 
      } else { 
         updateView(indicatorImage:  imageLiteral(resourceName: "Checkmark")) 
   } 
}


Then call


func textFieldShouldReturn(_ textField: UITextField) -> Bool {
      textField.resignFirstResponder()
      textField.validateTextField() /// Can be improved??
      
      return true
  }

Let me repeat what I wrote above. Your approach tests the entry only when the user taps return. It would be better to test the entry each time the user changed their entry by adding (or removing) a character. That way you an I ndicate an acceptable entry is in place and change your marker from red to green. The classic example of this is the ‘acceptable‘ password. As soon as the entry is 8 characters and has an uppercase and lowercase letter the marker changes from red to green. You can accomplish this by using either textDidChangeNorification or textfield(_:shouldChangeCharactersIn:: replacementString:) rather than textfidShodReturn.

Thanks PBK, this is my next task on the list! I also ran into another issue specific to the phone number text field... I have set the content type for that specific text field to be "Telephone Number". The keypad comes up correctly as expected, the only problem is that there is no dismiss or return key to dismiss the keypad and move onto the next text field. Is this something I have to handle in code or is there a checkbox in the IB to have a return key?


Thanks!


Please see screenshot:

h ttps://drive.google.com/file/d/146wmvnwF98RYATjhNHpcG_jdVwWTsbyu/view?usp=sharing