Home > Mobile >  How to refactor multiple guard statements for textfield validation?
How to refactor multiple guard statements for textfield validation?

Time:11-12

I'm refactoring some code and I'm thinking how to refactor and make it less code.

The code is:

func textFieldDidEndEditing(_ textField: UITextField) {

    guard !firstTextField.text!.isEmpty else {
        firstTextField.showErrorMessage(message: "Please fill the field.")
        confirmButton.disable(.filled)
        return
    }
    guard !firstTextField.text!.isEmpty && firstTextField.text!.contains(words[firstWord-1]) else {
        firstTextField.showErrorMessage(message: "Wrong word. Please check again.")
        confirmButton.disable(.filled)
        return
    }
    firstTextField.hideErrorMessage()
    firstTextField.setCorrectLayout()
    
    guard !secondTextField.text!.isEmpty else {
        secondTextField.showErrorMessage(message: "Please fill the field.")
        confirmButton.disable(.filled)
        return
    }
    guard !secondTextField.text!.isEmpty && secondTextField.text!.contains(words[secondWord-1]) else {
        secondTextField.showErrorMessage(message: "Wrong word. Please check again.")
        confirmButton.disable(.filled)
        return
    }
    secondTextField.hideErrorMessage()
    secondTextField.setCorrectLayout()
    
    guard !thirdTextField.text!.isEmpty else {
        thirdTextField.showErrorMessage(message: "Please fill the field.")
        confirmButton.disable(.filled)
        return
    }
    guard !thirdTextField.text!.isEmpty && thirdTextField.text!.contains(words[thirdWord-1]) else {
        thirdTextField.showErrorMessage(message: "Wrong word. Please check again.")
        confirmButton.disable(.filled)
        return
    }
    thirdTextField.hideErrorMessage()
    thirdTextField.setCorrectLayout()
    
    guard !fourthTextField.text!.isEmpty else {
        fourthTextField.showErrorMessage(message: "Please fill the field.")
        confirmButton.disable(.filled)
        return
    }
    guard !fourthTextField.text!.isEmpty && fourthTextField.text!.contains(words[fourthWord-1]) else {
        fourthTextField.showErrorMessage(message: "Wrong word. Please check again.")
        confirmButton.disable(.filled)
        return
    }
    fourthTextField.hideErrorMessage()
    fourthTextField.setCorrectLayout()

    confirmButton.enable(.filled)
}

So the message is same, just some parameters changes, like textfield and int in words[].

I thought to use a function and just put the parameters in it, then call it multiple times, but unfortunately guard statement will return only that function and not even textFieldDidEndEnditing()

Is there a better way to validate it (to work same) with less code?

CodePudding user response:

You dont need to give spesific textField name in delegate method. You could use

func textFieldDidEndEditing(_ textField: UITextField) {

guard !textField.text!.isEmpty else {
    textField.showErrorMessage(message: "Please fill the field.")
    confirmButton.disable(.filled)
    return
}
guard !textField.text!.isEmpty && textField.text!.contains(words[textfield.tag-1]) else {
    textField.showErrorMessage(message: "Wrong word. Please check again.")
    confirmButton.disable(.filled)
    return
}
textField.hideErrorMessage()
textField.setCorrectLayout()

}

I dont know what is words array and firstWord,secondWord.. in question but even I know., your second guard won't execute any time . if the second guard condition false , it means first one is also false that means it gonna be execute without controlling others.

CodePudding user response:

First of all I would create an extension like this (notice that I also remove force unwrap to avoid crash):

extension UITextField {

    func isValid(with word: String) -> Bool {
        guard let text = !self.text,
              !text.isEmpty else {
            showErrorMessage(message: "Please fill the field.")
            return false    
        }

        guard text.contains(word) else {
            showErrorMessage(message: "Wrong word. Please check again.")
            return false
        }

        hideErrorMessage()
        setCorrectLayout()
    }
}

and then in simplest way, you can use it like this:

func textFieldDidEndEditing(_ textField: UITextField) {
    
    guard firstTextField.isValid(with: words[firstWord-1]),
          secondTextField.isValid(with: words[secondWord-1]),
          ... else {
        confirmButton.disable(.filled)
        return
    }

    confirmButton.enable(.filled)
}

This is the basics. You can further improve it by making a dictionary of text fields (as key) and Int that defines a position of right word as value:

let linkedFields: [UITextField: Int] = [
    firstTextField: firstWord,
    secondTextField: secondWord,
    ...
]

so now you can just loop like this:

func textFieldDidEndEditing(_ textField: UITextField) {
    for linkedField in linkedFields {
        guard linkedField.key.isValid(with: words[linkedField.vallue]) else {
            confirmButton.disable(.filled)
            return
        }
    }

    confirmButton.enable(.filled)
}

As you may notice I am not using the fact that you know WHICH field was just edited here, just like your logic goes. And if you want to show errors for all fields that were not filled at once, that could be all right. But if you want to show only the errors for fields user "touched" (but not the fields user didn't get to yet), you need to preserve a bit more information, and only check edited field in this function. I will leave this aspect out though, as this is not part of your question.

  • Related