Как создать UITextField со встроенной ошибкой проверки?

Я пытаюсь создать собственный 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 с закругленными углами и меткой ошибки ниже, которая может удовлетворить мои требования к изменению высоты.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
62
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Обновление: согласно вашему комментарию, я предполагаю, что это то, что вы ищете.

  1. Объявите базовый класс, назовем его ValidationTextField, который содержит три подпредставления, как показано ниже.
  2. Далее создайте подкласс для конкретной цели, я приведу пример 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()
}

Выход:

Это простое решение, но его нельзя использовать повторно, поэтому мне придется создавать подобную логику для каждого текстового поля. Есть ли способ сделать это, который не требует от меня делать это 10 раз, если у меня есть, скажем, 10 разных текстовых полей с разной проверкой? Крайний пример, я знаю, но даже больше, чем один, может превратиться в боль, переписывающую логику снова и снова.

thesuffering 11.04.2024 15:29

Я обновил ответ, возможно, вы захотите взглянуть. Однако я предлагаю разделить вопрос на два отдельно. Надеюсь, это поможет.

sonle 12.04.2024 03:59
Ответ принят как подходящий

Это похоже на то, что предложил Сонле. В итоге я переместил это в 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()
        }
    }
}

Другие вопросы по теме