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...?)
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!)
CodePudding user response:
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:
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":
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
}
}