// // TextEntryField.swift // VDS // // Created by Matt Bruce on 10/3/22. // import Foundation import UIKit import VDSColorTokens import VDSFormControlsTokens import Combine @objc(VDSInputField) public class InputField: EntryField, UITextFieldDelegate { //-------------------------------------------------- // MARK: - Enums //-------------------------------------------------- public enum FieldType: String, CaseIterable { case text, number, calendar, inlineAction, password, creditCard, tel, date, securityCode } //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- required public init() { super.init(frame: .zero) } public override init(frame: CGRect) { super.init(frame: .zero) } public required init?(coder: NSCoder) { super.init(coder: coder) } //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- internal var inputFieldStackView: UIStackView = { return UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .horizontal $0.distribution = .fill $0.spacing = 12 } }() //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- open var type: FieldType = .text { didSet { didChange() }} var _showError: Bool = false open override var showError: Bool { get { _showError } set { if !showSuccess && _showError != newValue { _showError = newValue didChange() } } } var _showSuccess: Bool = false open var showSuccess: Bool { get { _showSuccess } set { if !showError && _showSuccess != newValue { _showSuccess = newValue didChange() } } } open override var state: UIControl.State { get { var state = super.state if showSuccess { state.insert(.success) } return state } } open var successText: String? { didSet { didChange() }} open var helperTextPlacement: HelperTextPlacement = .bottom { didSet { didChange() }} //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- private var successLabel = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) $0.textPosition = .left $0.textStyle = .bodySmall } private var textField = UITextField().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.font = TextStyle.bodyLarge.font } public var textFieldTextColorConfiguration: AnyColorable = ViewColorConfiguration().with { $0.setSurfaceColors(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark, forDisabled: true) $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forDisabled: false) }.eraseToAnyColorable() internal var minWidthConstraint: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- open override func setup() { super.setup() minWidthConstraint = containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: 0) minWidthConstraint?.isActive = true controlContainerView.addSubview(textField) textField.pinToSuperView() textField.heightAnchor.constraint(equalToConstant: 20).isActive = true textField .textPublisher .sink { [weak self] text in self?.value = text self?.sendActions(for: .valueChanged) }.store(in: &subscribers) stackView.addArrangedSubview(successLabel) stackView.setCustomSpacing(8, after: successLabel) successLabel.textColorConfiguration = primaryColorConfig.eraseToAnyColorable() backgroundColorConfiguration.setSurfaceColors(VDSColor.feedbackSuccessBackgroundOnlight, VDSColor.feedbackSuccessBackgroundOndark, forState: .success) borderColorConfiguration.setSurfaceColors(VDSColor.feedbackSuccessOnlight, VDSColor.feedbackSuccessOndark, forState: .success) } public override func reset() { super.reset() textField.text = "" textField.delegate = self successLabel.reset() successLabel.textPosition = .left successLabel.textStyle = .bodySmall type = .text showSuccess = false successText = nil helperTextPlacement = .bottom } open override func getContainer() -> UIView { inputFieldStackView.addArrangedSubview(containerView) return inputFieldStackView } open override func updateView() { super.updateView() textField.isEnabled = isEnabled textField.textColor = textFieldTextColorConfiguration.getColor(self) //show error or success if showError, let _ = errorText { successLabel.isHidden = true } else if showSuccess, let successText { successLabel.text = successText successLabel.surface = surface successLabel.disabled = disabled successLabel.isHidden = false errorLabel.isHidden = true icon.name = .checkmarkAlt icon.color = .black icon.surface = surface icon.isHidden = disabled } else { icon.isHidden = true successLabel.isHidden = true } //set the width constraints if let width, width > type.width { widthConstraint?.constant = width widthConstraint?.isActive = true minWidthConstraint?.isActive = false } else { minWidthConstraint?.constant = type.width widthConstraint?.isActive = false minWidthConstraint?.isActive = true } } open override func updateHelperLabel(){ //remove first helperLabel.removeFromSuperview() super.updateHelperLabel() //set the helper label position if helperText != nil { if helperTextPlacement == .right { inputFieldStackView.spacing = 12 inputFieldStackView.distribution = .fillEqually inputFieldStackView.addArrangedSubview(helperLabel) } else { inputFieldStackView.spacing = 0 inputFieldStackView.distribution = .fill stackView.addArrangedSubview(helperLabel) } } } } extension InputField.FieldType { var width: CGFloat { switch self { case .inlineAction: return 102 case .password: return 62.0 case .creditCard: return 288.0 case .tel: return 176.0 case .date: return 114.0 case .securityCode: return 88.0 default: return 40.0 } } }