Im trying to build a custom UITextField that shows a validation error inline below the textfield akin to material design. Im adding a UILabel lazily and constraining to the textfield when i present the error
private lazy var errorLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14, weight: .bold)
label.textColor = errorColor
label.numberOfLines = 0
label.isHidden = true
return label
}()
open override func didMoveToSuperview() {
super.didMoveToSuperview()
// Make sure that errorLabel is added once to the superview, not to the text field itself
if let superview = superview, errorLabel.superview == nil {
superview.addSubview(errorLabel)
configureErrorLabelConstraints()
}
}
private func configureErrorLabelConstraints() {
errorLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
errorLabel.topAnchor.constraint(equalTo: self.bottomAnchor, constant: 5),
errorLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor),
errorLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor)
])
public func showInlineValidationError(message: String) {
errorLabel.textColor = errorColor
layer.borderColor = errorColor.cgColor
errorLabel.text = message
errorLabel.isHidden = false
}
public func hideInlineValidationError() {
layer.borderColor = borderColor.cgColor
errorLabel.isHidden = true
}
}
This all works great. My issue is, say i have a UIImage constrained 12 points below the custom textfield, when the label is presented it does not move the image down by the label height, and rather reduces the space between the two. What i want is if the label is displayed, the constraint between the textfield and image to be increased by how much additional height the label takes up. How can i go about this? AI has not been helpful at all in what i might be able to do here.
Im open to a 3rd party library if its basic enough. I just need a basic UITextfield with rounded corners and the error label below that can support my height change requirements
This is similar to what sonle suggested. I ended up moving this to a UIView containing a stackview and hide/show the label. if i have a height constraint it must be set to greater than or equal to so the view is allow to grow with the stackview.
open class ErrorTextField: UIView {
public var errorColor: UIColor = .red
public var errorLabelTopSpacing: CGFloat = 5
public var textfieldHeight = 48
public var textfieldBorderWidth: CGFloat = 1
public var textfieldCornerRadius: CGFloat = 4
public var errorLabelFont: UIFont = .systemFont(ofSize: 14, weight: .bold)
public let textField = UITextField()
public var errorLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.isHidden = true
return label
}()
private lazy var stackView: UIStackView = {
let stack = UIStackView(arrangedSubviews: [textField, errorLabel])
return stack
}()
override public init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupViews()
}
private func setupViews() {
textField.layer.cornerRadius = textfieldCornerRadius
textField.layer.borderWidth = textfieldBorderWidth
errorLabel.font = errorLabelFont
errorLabel.textColor = errorColor
stackView.axis = .vertical
stackView.spacing = errorLabelTopSpacing
addSubview(stackView)
textField.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
textField.heightAnchor.constraint(equalToConstant: CGFloat(textfieldHeight)),
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor)
])
}
public func showError(message: String) {
errorLabel.text = message
textField.borderColor = errorColor
UIView.animate(withDuration: 0.25, delay: .zero, options: .curveEaseOut) {
self.errorLabel.isHidden = false
self.stackView.layoutIfNeeded()
}
}
public func hideError() {
UIView.animate(withDuration: 0.25) {
self.errorLabel.isHidden = true
self.stackView.layoutIfNeeded()
}
}
}