Я пытаюсь создать собственный UITextField, который отображает ошибку проверки под текстовым полем, что похоже на дизайн материала. Я лениво добавляю UILabel и ограничиваю текстовое поле, когда представляю ошибку
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
}
}
Все это прекрасно работает. Моя проблема в том, что, скажем, у меня есть UIImage, ограниченный на 12 пунктов ниже пользовательского текстового поля, когда отображается метка, она не перемещает изображение вниз на высоту метки, а скорее уменьшает пространство между ними. Я хочу, чтобы если метка отображалась, ограничение между текстовым полем и изображением увеличивалось на величину дополнительной высоты, которую занимает метка. Как я могу это сделать? ИИ совершенно не помог мне в том, что я мог бы сделать здесь.
Я открыт для сторонней библиотеки, если она достаточно проста. Мне просто нужно базовое поле UITextfield с закругленными углами и меткой ошибки ниже, которая может удовлетворить мои требования к изменению высоты.
Обновление: согласно вашему комментарию, я предполагаю, что это то, что вы ищете.
ValidationTextField
, который содержит три подпредставления, как показано ниже.EmailTextField
.class ValidationTextField: UIView {
struct Config {
let keyboardType: UIKeyboardType
let error: String
//Other configures if needed
}
private var textField = UITextField()
private var errorLabel = UILabel()
private var errorImage = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
//Add subViews within stackView here
textField.addTarget(self, action: #selector(editingChanged(_:)), for: .editingChanged)
}
func bind(_ config: ValidationTextField.Config) {
textField.keyboardType = config.keyboardType
errorLabel.text = config.error
}
func validate(_ text: String?) -> Bool {
//TODO: should be overridden
return true
}
private func toggleError(isShow: Bool) {
guard (!errorLabel.isHidden && !isShow) || (errorLabel.isHidden && isShow) else { return }
UIView.animate(withDuration: 0.3) {
self.errorLabel.isHidden = !isShow
}
}
@objc private func editingChanged(_ textField: UITextField) {
let isValid = validate(textField.text)
toggleError(isShow: !isValid)
}
}
А вот как может выглядеть EmailTextField
:
final class EmailTextField: ValidationTextField {
override init(frame: CGRect) {
super.init(frame: frame)
bind(.init(keyboardType: .emailAddress,
error: "Email is not valid"))
}
override func validate(_ text: String?) -> Bool {
if let text {
return validateEmail(text)
} else {
return true
}
}
private func validateEmail(_ text: String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
let emailTest = NSPredicate(format:"SELF MATCHES %@", emailRegEx)
return emailTest.evaluate(with: text)
}
Я думаю, что лучший подход для этого сценария — добавить контейнер StackView, который будет содержать все textField
, errorLabel
и imageView
по вертикали.
Затем вы можете просто переключить errorLabel
независимо от высоты метки и ограничений между TextField и ImageView.
UIView.animate(withDuration: 0.3) {
self.errorLabel.isHidden.toggle()
}
Выход:
Я обновил ответ, возможно, вы захотите взглянуть. Однако я предлагаю разделить вопрос на два отдельно. Надеюсь, это поможет.
Это похоже на то, что предложил Сонле. В итоге я переместил это в UIView, содержащий вид стека, и скрыл/показывал метку. если у меня есть ограничение по высоте, оно должно быть установлено больше или равно, чтобы представление могло расти вместе с представлением стека.
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()
}
}
}
Это простое решение, но его нельзя использовать повторно, поэтому мне придется создавать подобную логику для каждого текстового поля. Есть ли способ сделать это, который не требует от меня делать это 10 раз, если у меня есть, скажем, 10 разных текстовых полей с разной проверкой? Крайний пример, я знаю, но даже больше, чем один, может превратиться в боль, переписывающую логику снова и снова.