35 Replies
      Latest reply on Jul 19, 2018 7:54 AM by mkhan094
      mkhan094 Level 1 Level 1 (0 points)

        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!

        • Re: Multiple Text Fields Invalid Entry Problem
          PBK Level 6 Level 6 (2,735 points)

          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

          }

            • Re: Multiple Text Fields Invalid Entry Problem
              mkhan094 Level 1 Level 1 (0 points)

              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!
              
              
                • Re: Multiple Text Fields Invalid Entry Problem
                  junkpile Level 6 Level 6 (2,345 points)

                  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.

                    • Re: Multiple Text Fields Invalid Entry Problem
                      mkhan094 Level 1 Level 1 (0 points)

                      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

                      • Re: Multiple Text Fields Invalid Entry Problem
                        mkhan094 Level 1 Level 1 (0 points)

                        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

                          • Re: Multiple Text Fields Invalid Entry Problem
                            Claude31 Level 7 Level 7 (4,335 points)

                            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

                              • Re: Multiple Text Fields Invalid Entry Problem
                                mkhan094 Level 1 Level 1 (0 points)

                                Okay. let me try this out.. thanks

                                • Re: Multiple Text Fields Invalid Entry Problem
                                  PBK Level 6 Level 6 (2,735 points)

                                  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.

                                    • Re: Multiple Text Fields Invalid Entry Problem
                                      mkhan094 Level 1 Level 1 (0 points)

                                      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
                                        }
                                       }
                                      
                                        • Re: Multiple Text Fields Invalid Entry Problem
                                          Claude31 Level 7 Level 7 (4,335 points)

                                          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
                                            }
                                          • Re: Multiple Text Fields Invalid Entry Problem
                                            PBK Level 6 Level 6 (2,735 points)

                                            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.

                                              • Re: Multiple Text Fields Invalid Entry Problem
                                                mkhan094 Level 1 Level 1 (0 points)

                                                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

                                                • Re: Multiple Text Fields Invalid Entry Problem
                                                  mkhan094 Level 1 Level 1 (0 points)

                                                  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!

                                                    • Re: Multiple Text Fields Invalid Entry Problem
                                                      Claude31 Level 7 Level 7 (4,335 points)

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

                                                        • Re: Multiple Text Fields Invalid Entry Problem
                                                          mkhan094 Level 1 Level 1 (0 points)

                                                          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
                                                                
                                                                
                                                             
                                                                
                                                             }
                                                             
                                                          
                                                            • Re: Multiple Text Fields Invalid Entry Problem
                                                              PBK Level 6 Level 6 (2,735 points)

                                                              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.

                                                                • Re: Multiple Text Fields Invalid Entry Problem
                                                                  mkhan094 Level 1 Level 1 (0 points)

                                                                  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
                                                                        
                                                                        
                                                                     
                                                                        
                                                                     }
                                                                     
                                                                  
                                                                    • Re: Multiple Text Fields Invalid Entry Problem
                                                                      Claude31 Level 7 Level 7 (4,335 points)

                                                                      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"))
                                                                                }
                                                                           }
                                                                        • Re: Multiple Text Fields Invalid Entry Problem
                                                                          PBK Level 6 Level 6 (2,735 points)

                                                                          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.

                                                                            • Re: Multiple Text Fields Invalid Entry Problem
                                                                              Claude31 Level 7 Level 7 (4,335 points)

                                                                              You're right.

                                                                               

                                                                              Editing did change event is called on every key stroke.

                                                                                • Re: Multiple Text Fields Invalid Entry Problem
                                                                                  mkhan094 Level 1 Level 1 (0 points)

                                                                                  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
                                                                                  
                                                  • Re: Multiple Text Fields Invalid Entry Problem
                                                    Claude31 Level 7 Level 7 (4,335 points)

                                                    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
                                                         }
                                                    }