Search code examples
iosswiftuikit

How to change UIView "backgroundColor" and resize it appending text in UITextField (validation)? / Swift 5



Hello everyone!) Need some help!)

I have password validation text field and right now I'm setting UI. In text field I have only four rules. I created validation line which is UIView which has width - 348. The width and color of UIView must change every time if we add one rule to text field. If we have four rules, we must divide validation line by four: 348 / 4 = 87, and create four colors for it like red, orange, yellow and green.

How can I make the UIView available to update the width and color every time I add or subtract one rule in text field, like one lowercased character, digits, one uppercased character, ect...?)

enter image description here


Text field with four rules:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    
    guard let text = validationTextField.text else { return true }
    guard let textRange = Range(range, in: text) else { return true }
    let updatedText = text.replacingCharacters(in: textRange, with: string)
    
    //Minimum eight characters
    if updatedText.count >= 8 {
        eightCharsLablel.text = "⎷ minimum of 8 characters."
        eightCharsLablel.textColor = #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1)
        textFieldValidLineView.frame.size.width = 0 + 87

    } else {
        eightCharsLablel.text = "– minimum of 8 characters."
        eightCharsLablel.textColor = #colorLiteral(red: 0.2849253164, green: 0.1806431101, blue: 0.5, alpha: 1)
    }

    //Minimum one digit
    if updatedText.range(of: #"\d+"#, options: .regularExpression) != nil {
        oneDigitLablel.text = "⎷ minimum 1 digit."
        oneDigitLablel.textColor = #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1)
        textFieldValidLineView.frame.size.width = 0 + 87
    } else {
        oneDigitLablel.text = "– minimum 1 digit."
        oneDigitLablel.textColor = #colorLiteral(red: 0.2849253164, green: 0.1806431101, blue: 0.5, alpha: 1)
    }

    //Minimum one lowercased
    if updatedText.range(of: #".*[a-z]+.*"#, options: .regularExpression) != nil {
        oneLowercasedLablel.text = "⎷ minimum 1 lowercased."
        oneLowercasedLablel.textColor = #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1)
        
    } else {
        oneLowercasedLablel.text = "– minimum 1 lowercased."
        oneLowercasedLablel.textColor = #colorLiteral(red: 0.2849253164, green: 0.1806431101, blue: 0.5, alpha: 1)
    }

    //Minimum one uppercased
    if updatedText.range(of: #".*[A-Z]+.*"#, options: .regularExpression) != nil {
        oneUppercasedLablel.text = "⎷ minimum 1 uppercased."
        oneUppercasedLablel.textColor = #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1)

    } else {
        oneUppercasedLablel.text = "– minimum 1 uppercased."
        oneUppercasedLablel.textColor = #colorLiteral(red: 0.2849253164, green: 0.1806431101, blue: 0.5, alpha: 1)
    }

    //No whitespaces
    if updatedText.range(of: #"\s+"#, options: .regularExpression) != nil {
        return false
    }
    return true
    
}

Thanks for every answer!)


Solution

  • One way to do this that avoids any size calculations is to use a Horizontal UIStackView for your "ValidationLine".

    If we set the stack view .distribution = .fillEqually, and then add a view for each "rule", the layout will happen automatically:

    enter image description here

    Then, to "grow and color" the "line" we can set the background colors of the arranged subviews based on how many rules have been "met":

    enter image description here

    Here's a complete example you can try out:

    class ViewController: UIViewController, UITextFieldDelegate {
        
        let ruleColors: [UIColor] = [
            .red, .orange, .yellow, .green,
        ]
        
        let validationTextField = UITextField()
        let eightCharsLablel = UILabel()
        let oneDigitLablel = UILabel()
        let oneLowercasedLablel = UILabel()
        let oneUppercasedLablel = UILabel()
        
        // horizontal Stack View
        let validationLineStackView: UIStackView = {
            let v = UIStackView()
            v.axis = .horizontal
            v.distribution = .fillEqually
            v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
            return v
        }()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            // add one view for each rule to the validationLineStackView
            //  we'll set the background colors when we satisfy rules
            for _ in 0..<ruleColors.count {
                let v = UIView()
                v.backgroundColor = .clear
                validationLineStackView.addArrangedSubview(v)
            }
            
            // put everything in a vertical stack view for this example
            let stackView = UIStackView()
            stackView.axis = .vertical
            stackView.spacing = 8
            
    
            stackView.addArrangedSubview(validationTextField)
            stackView.addArrangedSubview(validationLineStackView)
            stackView.addArrangedSubview(eightCharsLablel)
            stackView.addArrangedSubview(oneDigitLablel)
            stackView.addArrangedSubview(oneLowercasedLablel)
            stackView.addArrangedSubview(oneUppercasedLablel)
            
            stackView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(stackView)
            
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                
                // stack view Top/Leading/Trailing with 20-points "padding"
                stackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
                stackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                stackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
                
                // we'll use the intrinsic heights for the text field and all labels
                //  but we need to set the height of the validationLineStackView
                validationLineStackView.heightAnchor.constraint(equalToConstant: 8.0),
                
            ])
            
            // to make it easier to see the text field
            validationTextField.borderStyle = .roundedRect
            validationTextField.backgroundColor = .cyan
            
            validationTextField.delegate = self
            
            // initial update
            updateRulesProgress("")
        }
        
        func updateRulesProgress(_ updatedText: String) {
            
            var numRulesMet: Int = 0
            
            //Minimum eight characters
            if updatedText.count >= 8 {
                eightCharsLablel.text = "⎷ minimum of 8 characters."
                eightCharsLablel.textColor = #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1)
                // increment our "met" rules counter
                numRulesMet += 1
            } else {
                eightCharsLablel.text = "– minimum of 8 characters."
                eightCharsLablel.textColor = #colorLiteral(red: 0.2849253164, green: 0.1806431101, blue: 0.5, alpha: 1)
            }
            
            //Minimum one digit
            if updatedText.range(of: #"\d+"#, options: .regularExpression) != nil {
                oneDigitLablel.text = "⎷ minimum 1 digit."
                oneDigitLablel.textColor = #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1)
                // increment our "met" rules counter
                numRulesMet += 1
            } else {
                oneDigitLablel.text = "– minimum 1 digit."
                oneDigitLablel.textColor = #colorLiteral(red: 0.2849253164, green: 0.1806431101, blue: 0.5, alpha: 1)
            }
            
            //Minimum one lowercased
            if updatedText.range(of: #".*[a-z]+.*"#, options: .regularExpression) != nil {
                oneLowercasedLablel.text = "⎷ minimum 1 lowercased."
                oneLowercasedLablel.textColor = #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1)
                // increment our "met" rules counter
                numRulesMet += 1
            } else {
                oneLowercasedLablel.text = "– minimum 1 lowercased."
                oneLowercasedLablel.textColor = #colorLiteral(red: 0.2849253164, green: 0.1806431101, blue: 0.5, alpha: 1)
            }
            
            //Minimum one uppercased
            if updatedText.range(of: #".*[A-Z]+.*"#, options: .regularExpression) != nil {
                oneUppercasedLablel.text = "⎷ minimum 1 uppercased."
                oneUppercasedLablel.textColor = #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1)
                // increment our "met" rules counter
                numRulesMet += 1
            } else {
                oneUppercasedLablel.text = "– minimum 1 uppercased."
                oneUppercasedLablel.textColor = #colorLiteral(red: 0.2849253164, green: 0.1806431101, blue: 0.5, alpha: 1)
            }
            
            // now update the background colors of the views in the validationLineStackView
            for i in 0..<validationLineStackView.arrangedSubviews.count {
                if i < numRulesMet {
                    validationLineStackView.arrangedSubviews[i].backgroundColor = ruleColors[numRulesMet - 1]
                } else {
                    validationLineStackView.arrangedSubviews[i].backgroundColor = .clear
                }
            }
            
        }
        
        func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
            
            guard let text = validationTextField.text else { return true }
            guard let textRange = Range(range, in: text) else { return true }
            let updatedText = text.replacingCharacters(in: textRange, with: string)
            
            // make this the first IF case, since we'll return without allowing the
            //  text to change -- so no need to check anything else
            //No whitespaces
            if updatedText.range(of: #"\s+"#, options: .regularExpression) != nil {
                return false
            }
            
            // move all the rule IFs to the updateRulesProgress function
            updateRulesProgress(updatedText)
            
            return true
            
        }
    }