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

I am having trouble with this method: textfield(_:shouldChangeCharactersIn:: replacementString:). Basically, I am just calling my validateTextField method inside this and returning a true:


func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
      
      textField.validateNameTextField()
      
      return true
   }
   
  


The problem that I am getting here is that the user has to type in at least 2 characters for the check mark to appear. The other issues is that when the user hits the backspace and clears out the text field, the check mark does'nt change back to an asterisk.



I am tackling this problem text field at a time, so for the name text fields, even if they type in one character that is theoretically fine. For email or password, there will be stricter validation rules.


Thanks!

You should not return true systematically, but upon a positive test of validitry.

I still can't seem to figure it out.. here is my logic, and now if I type text into the text field I cannot see any text being typed.


// Validate Text in Text Field
func validate(string: String) -> Bool {
      
      var isEntryValid: Bool
      
      if (string == "") {
         
         isEntryValid = false
         
      } else {
         
         isEntryValid = true
         
         
      }
      
      return isEntryValid
   }
   
   

   
   func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
      
      var result: Bool
      

      if validate(string: textField.text!) {
         // updateRightView already discussed in this forum thread..

         textField.updateRightView(withIndicatorImage:  imageLiteral(resourceName: "Checkmark"))
         result = true
         
      } else {
         
         textField.updateRightView(withIndicatorImage:  imageLiteral(resourceName: "Asterisk"))
         result = true
         
      }

      
      return result
      
      
   
      
   }
  

Typing on iPhone not laptop - 1) you want to always return true. You are intercepting the keystroke to set that asterisk or checkmark not to interfere with typing. 2) what the textField contains is only the earlier keystrokes. What the user intends it to contain, and what the check mark/asterisk should be judging, is the current textfield.text plus (or minus) the replacement text. So you need to append the text that your method is judging OR you can do this - call your validation method ‘afterDelay’ of about 0.1 seconds to give the textfield time to respond to your return YES.

Thanks for the tip... this worked! I just used DispatchQueue and cleaned up the validateMethod to make it a single call. DispatchQueue is something that I am new too, thought I have heard that it is not a good idea to have things on the main queue since that is where the UI work is done? Would my code be okay or should I consider modifying it?


func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
      
      DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
        
         textField.validatePlainTextField()
      
     
      }

      
      return true
      
      
   
      
   }
  

All actions that involve UIKit should occur on the main thread.


But that blocks user interaction during the delay ; 0.1 s is not a big issue, but that's not ideal.


What you could do is to create an IBAction for Editing did change event of the text field


create an IBOutlet for the textField myTextField


     @IBAction func editingTestField(_ sender: UITextField) {

          let newLength = sender.text!.count
          lengthLabel.text = "\(newLength)/5"
          if validate(string: sender.text!) {
              myTextField.updateRightView(withIndicatorImage:  imageLiteral(resourceName: "Checkmark"))
          } else {
              myTextField.updateRightView(withIndicatorImage:  imageLiteral(resourceName: "Asterix"))
          }
     }

While dispatch_main will lock the main queue, I don’t think DispatchQueue.mail.asynchAfter blocks anything especially because of the ‘a’ in ‘asynch’. Can’t be sure because Swift is Greek to me. Your IBAction would need to be called on each keystroke to work as desired.

You're right.


Editing did change event is called on every key stroke.

Thanks for the help everyone.. I making a lot of progress. I've moved onto the phone number validations. Currently, we only have 2 customers outside the US and the rest are in the US. What I want to do is to validate just the US number and format it like so:


Before: 4089961010

After: (408) 996-1010


If the the phone number is > 10 digits, then format it like so for now: 40899610101

I wrote a simple method to achieve this:


func formatUSNumber(withNumber number: String) -> String {
      
      
      var formattedNumber = number
      
      
      if number.count == 10 {
         
         // Before This Line: 4089961010
         // After  This Line: (4089961010
         formattedNumber.insert("(", at: formattedNumber.startIndex)
         
         // Before This Line: (4089961010
         // After  This Line: (408)9961010
         formattedNumber.insert(")", at: formattedNumber.index(formattedNumber.startIndex, offsetBy: 4))
         
         
         // Before This Line: (408)9961010
         // After  This Line: (408) 9961010
         formattedNumber.insert(" ", at: formattedNumber.index(formattedNumber.startIndex, offsetBy: 5))
         
         // Before This Line: (408) 9961010
         // After  This Line: (408) 996-1010
         formattedNumber.insert("-", at: formattedNumber.index(formattedNumber.startIndex, offsetBy: 9))
         
         
         return formattedNumber
         
      } else {
         
         
         return number
         
      }
      
      
      
      
   }


Then I call this method in my validatePhoneTextField method:


func validatePhoneNumberTextField() {
      
      
      if((self.text?.count)! >= 10) {
         
         
         updateRightView(withIndicatorImage:  imageLiteral(resourceName: "Checkmark"))
         self.text = formatUSNumber(withNumber: self.text!)
      
         
      } else {
         
         updateRightView(withIndicatorImage:  imageLiteral(resourceName: "Asterisk"))
         
      }
}



Then I call this method in textfield(_:shouldChangeCharactersIn:: replacementString:)


func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
      

     // OTHER TEXT FIELDS HERE....
      
      
      if textField == phoneNumberTextField {
         
         DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            
            textField.validatePhoneNumberTextField()
            
         }
         
      }

      
      return true
      
   }


The number formats correctly as soon as then tenth character is typed in, the problem happens when the user types in more than 10 characters this is what happens:


(408) 996-101022222.


I tested my method in a Swift Playground and can confirm that after 10 characters the number should format to the original style... see the results below. I'm guessing the problem us somewhere in:


validatePhoneTextField() or textfield(_:shouldChangeCharactersIn:: replacementString:)




let phone = "4089961010"
var longPhone = "408996101022222"


formatUSNumber(withNumber: phone)     // RESULT FROM PLAYGROUND: (408) 996-1010
     
formatUSNumber(withNumber: longPhone) // RESULT FROM PLAYGROUND: 408996101022222

When you type the 11th digit, you should remove it;


that could be done IBAction on editing did change


     @IBAction func editingTestField(_ sender: UITextField) {

          let newLength = sender.text!.count
          if newLength > 10 {
               sender.text = String(sender.text!.prefix(10))     // String needed as prefix returns a SubString
          }
     }

I didn't quite understand what your code is doing. An explanation would be great, also, would this technique be possible to do in my formatUSNumber( ) method or does it only make sense to implement that in an IBAction method?


Thanks

What it does is to refuse characters beyond the 10th.


So you can type the 10 digits, but when you type beyond, they just are refused.

For user feedback, you could emit a beep in that case to signal the error.

Oh ok, I see what you're saying.

So tell us if that works. And don't forget to close this (long) thread.

Thanks for the help everyone, I do have one more question, it probably has a simple answer... When the phoneNumberTextField is tapped, I've set the keyboard type property in Xcode IB to be Phone Pad. The problem is that the phone doesnt have a return key, so I cannot dismiss it to move onto the next text field. Is there a special method that I overlooked... there's gotta be something?