// // TextEntryField.swift // VDS // // Created by Matt Bruce on 10/3/22. // import Foundation import UIKit import VDSColorTokens import VDSFormControlsTokens import Combine public enum TextEntryFieldType: String, CaseIterable { case text, number, calendar, inlineAction, password, creditCard, tel, date, securityCode } @objc(VDSTextEntryField) public class TextEntryField: TextEntryFieldBase{} open class TextEntryFieldBase: EntryField { //-------------------------------------------------- // 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 containerStackView: UIStackView = { return UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .horizontal $0.distribution = .fill $0.spacing = 12 } }() //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- open var type: TextEntryFieldType = .text { didSet { didChange() }} open var showSuccess: Bool = false { didSet { 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() }} private var successLabel = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) $0.textPosition = .left $0.typograpicalStyle = .BodySmall } internal var minWidthConstraint: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- open override func setup() { super.setup() minWidthConstraint = containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: 0) minWidthConstraint?.isActive = true stackView.addArrangedSubview(successLabel) stackView.setCustomSpacing(8, after: successLabel) successLabel.textColorConfiguration = primaryColorConfig.eraseToAnyColorable() } public override func reset() { super.reset() successLabel.reset() successLabel.textPosition = .left successLabel.typograpicalStyle = .BodySmall type = .text showSuccess = false successText = nil helperTextPlacement = .bottom } open override func getContainer() -> UIView { containerStackView.addArrangedSubview(containerView) return containerStackView } open override func getBackgroundConfig() -> AnyColorable { return TextEntryFieldColorConfiguration().with { $0.enabled.lightColor = VDSFormControlsColor.backgroundOnlight $0.enabled.darkColor = VDSFormControlsColor.backgroundOndark $0.disabled.lightColor = VDSFormControlsColor.backgroundOnlight $0.disabled.darkColor = VDSFormControlsColor.backgroundOndark //error/success doesn't care enabled/disable $0.error.lightColor = VDSColor.feedbackErrorBackgroundOnlight $0.error.darkColor = VDSColor.feedbackErrorBackgroundOndark $0.success.lightColor = VDSColor.feedbackSuccessBackgroundOnlight $0.success.darkColor = VDSColor.feedbackSuccessBackgroundOndark }.eraseToAnyColorable() } open override func getBorderConfig() -> AnyColorable { return TextEntryFieldColorConfiguration().with { $0.enabled.lightColor = VDSFormControlsColor.borderOnlight $0.enabled.darkColor = VDSFormControlsColor.borderOnlight $0.disabled.lightColor = VDSColor.interactiveDisabledOnlight $0.disabled.darkColor = VDSColor.interactiveDisabledOndark //error/success doesn't care enabled/disable $0.error.lightColor = VDSColor.feedbackErrorOnlight $0.error.darkColor = VDSColor.feedbackErrorOndark $0.success.lightColor = VDSColor.feedbackSuccessOnlight $0.success.darkColor = VDSColor.feedbackSuccessOndark }.eraseToAnyColorable() } //-------------------------------------------------- // MARK: - State //-------------------------------------------------- open override func updateView() { super.updateView() //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 } else { 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 { containerStackView.spacing = 12 containerStackView.distribution = .fillEqually containerStackView.addArrangedSubview(helperLabel) } else { containerStackView.spacing = 0 containerStackView.distribution = .fill stackView.addArrangedSubview(helperLabel) } } } internal class TextEntryFieldColorConfiguration: DisabledSurfaceColorable { var success = SurfaceColorConfiguration() var error = SurfaceColorConfiguration() var disabled = SurfaceColorConfiguration() var enabled = SurfaceColorConfiguration() required init(){} func getColor(_ object: TextEntryField) -> UIColor { //only show error is enabled and showError == true let showErrorColor = !object.disabled && object.showError let showSuccessColor = !object.disabled && object.showSuccess if showErrorColor { return error.getColor(object) } else if showSuccessColor { return success.getColor(object) } else { return getDisabledColor(object) } } } } extension TextEntryFieldType { 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 } } }