diff --git a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift index 72d8cb41..401208b8 100644 --- a/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/DropdownEntryField.swift @@ -16,6 +16,14 @@ import UIKit private var calendar: Calendar? + //-------------------------------------------------- + // MARK: - Accessories + //-------------------------------------------------- + + public weak var datePicker: UIDatePicker? + public var pickerView: UIPickerView? + + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift index 79b1328e..7fe63f97 100644 --- a/MVMCoreUI/Atoms/TextFields/FormEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/FormEntryField.swift @@ -22,10 +22,10 @@ import UIKit private(set) var fieldContainer: UIView? private var borderStrokeColor: UIColor = UIColor.mfSilver() - private var borderPath: UIBezierPath? + private var borderPath: UIBezierPath = UIBezierPath() public var bottomBar: UIView? - public var dashLine: DashLine? + var delegateObject: MVMCoreUIDelegateObject? //-------------------------------------------------- // MARK: - Properties @@ -38,13 +38,7 @@ import UIKit public var hideBorder = false public var showErrorMessage = false - public var errorMessage: String? { - didSet { - if showErrorMessage { - feedback = errorMessage - } - } - } + public var errorMessage: String? /// Toggles the enables state of this component. public var isEnabled = true { @@ -52,12 +46,7 @@ import UIKit DispatchQueue.main.async { [weak self] in guard let self = self else { return } - self.isUserInteractionEnabled = self.isEnabled - self.feedbackLabel?.text = nil - self.descriptionLabel?.textColor = self.isEnabled ? UIColor.mfBattleshipGrey() : UIColor.mfSilver() - self.bottomBar?.backgroundColor = self.isEnabled ? (self.showErrorMessage ? UIColor.mfPumpkin() : .black) : UIColor.mfSilver() - self.fieldContainer?.setNeedsDisplay() - self.fieldContainer?.layoutIfNeeded() + self.isEnabled ? self.originalAppearance() : self.errorAppearance() } } } @@ -80,20 +69,13 @@ import UIKit public var feedback: String? { get { return feedbackLabel?.text } set { - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - self.feedbackLabel?.text = newValue - self.bottomBarHeightConstraint?.constant = self.showErrorMessage ? 4 : 1 - self.bottomBar?.backgroundColor = self.showErrorMessage ? UIColor.mfPumpkin() : .black - self.setNeedsDisplay() - self.layoutIfNeeded() - } - + self.feedbackLabel?.text = newValue + self.refreshUI() setAccessibilityString(newValue) } } + // TODO: Pull this out into Styler or some class akin to it. public var formatter: DateFormatter = { let formatter = DateFormatter() @@ -109,11 +91,11 @@ import UIKit // MARK: - Constraints //-------------------------------------------------- - public var textContainerLeading: NSLayoutConstraint? - public var textContainerTrailing: NSLayoutConstraint? + public var fieldContainerLeading: NSLayoutConstraint? + public var fieldContainerTrailing: NSLayoutConstraint? - public var errorLabelTrailing: NSLayoutConstraint? - public var errorLabelLeading: NSLayoutConstraint? + public var feedbackLabelTrailing: NSLayoutConstraint? + public var feedbackLabelLeading: NSLayoutConstraint? public var descriptionLabelLeading: NSLayoutConstraint? public var descriptionLabelTrailing: NSLayoutConstraint? @@ -149,9 +131,8 @@ import UIKit final public override func setupView() { guard subviews.isEmpty else { return } - + // heightAnchor.constraint(greaterThanOrEqualToConstant: 0).isActive = true translatesAutoresizingMaskIntoConstraints = false - setContentHuggingPriority(.required, for: .vertical) setContentCompressionResistancePriority(.required, for: .vertical) backgroundColor = .clear @@ -159,9 +140,8 @@ import UIKit self.descriptionLabel = descriptionLabel descriptionLabel.font = MFStyler.fontB3() descriptionLabel.textColor = UIColor.mfBattleshipGrey() - descriptionLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) - descriptionLabel.setContentHuggingPriority(UILayoutPriority(251), for: .vertical) descriptionLabel.setContentCompressionResistancePriority(.required, for: .vertical) + descriptionLabel.setContentCompressionResistancePriority(.required, for: .horizontal) addSubview(descriptionLabel) @@ -179,35 +159,35 @@ import UIKit setupFieldContainer(fieldContainer) fieldContainer.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 4).isActive = true - textContainerLeading = fieldContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - textContainerLeading?.isActive = true - textContainerTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: fieldContainer.trailingAnchor) - textContainerTrailing?.isActive = true + fieldContainerLeading = fieldContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + fieldContainerLeading?.isActive = true + fieldContainerTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: fieldContainer.trailingAnchor) + fieldContainerTrailing?.isActive = true let feedbackLabel = Label() self.feedbackLabel = feedbackLabel feedbackLabel.font = MFStyler.fontForTextFieldUnderLabel() feedbackLabel.textColor = .black - feedbackLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) - feedbackLabel.setContentHuggingPriority(UILayoutPriority(251), for: .horizontal) feedbackLabel.setContentCompressionResistancePriority(.required, for: .vertical) + feedbackLabel.setContentCompressionResistancePriority(.required, for: .horizontal) addSubview(feedbackLabel) - feedbackLabel.topAnchor.constraint(equalTo: fieldContainer.bottomAnchor).isActive = true - errorLabelLeading = feedbackLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - errorLabelLeading?.isActive = true - errorLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: feedbackLabel.trailingAnchor) - errorLabelTrailing?.isActive = true + feedbackLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 0).isActive = true + feedbackLabel.topAnchor.constraint(equalTo: fieldContainer.bottomAnchor, constant: PaddingOne).isActive = true + feedbackLabelLeading = feedbackLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + feedbackLabelLeading?.isActive = true + feedbackLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: feedbackLabel.trailingAnchor) + feedbackLabelTrailing?.isActive = true layoutMarginsGuide.bottomAnchor.constraint(equalTo: feedbackLabel.bottomAnchor).isActive = true setNeedsLayout() } /** - Method to override. - Intended to add the interactive content (textField) to the fieldContainer. - */ + Method to override. + Intended to add the interactive content (textField) to the fieldContainer. + */ open func setupFieldContainerContent(_ container: UIView) { // To Be Overridden By Subclass. } @@ -229,19 +209,6 @@ import UIKit bottomBar.leadingAnchor.constraint(equalTo: parentView.leadingAnchor).isActive = true parentView.trailingAnchor.constraint(equalTo: bottomBar.trailingAnchor).isActive = true parentView.bottomAnchor.constraint(equalTo: bottomBar.bottomAnchor).isActive = true - - let dashLine = DashLine() - dashLine.translatesAutoresizingMaskIntoConstraints = false - dashLine.backgroundColor = .white - dashLine.isHidden = true - - parentView.addSubview(dashLine) - - NSLayoutConstraint.activate([ - dashLine.centerYAnchor.constraint(equalTo: bottomBar.centerYAnchor), - dashLine.centerXAnchor.constraint(equalTo: bottomBar.centerXAnchor), - dashLine.topAnchor.constraint(equalTo: bottomBar.topAnchor), - dashLine.leadingAnchor.constraint(equalTo: bottomBar.leadingAnchor)]) } open override func updateView(_ size: CGFloat) { @@ -249,9 +216,8 @@ import UIKit descriptionLabel?.updateView(size) feedbackLabel?.font = MFStyler.fontForTextFieldUnderLabel() - dashLine?.updateView(size) - layoutIfNeeded() + refreshUI() } //-------------------------------------------------- @@ -261,23 +227,22 @@ import UIKit open override func draw(_ rect: CGRect) { super.draw(rect) - borderPath?.removeAllPoints() + borderPath.removeAllPoints() if !hideBorder, let frame = fieldContainer?.frame { + // Brings the other half of the line inside the view to prevent thinness from cropping. let insetLean: CGFloat = 0.5 - borderPath = UIBezierPath() - borderPath?.lineWidth = 1 + borderPath.lineWidth = 1 - borderPath?.move(to: CGPoint(x: frame.origin.x + insetLean, y: frame.origin.y + frame.size.height)) - borderPath?.addLine(to: CGPoint(x: frame.origin.x + insetLean, y: frame.origin.y + insetLean)) - borderPath?.addLine(to: CGPoint(x: frame.origin.x + frame.size.width - insetLean, y: frame.origin.y + insetLean)) - borderPath?.addLine(to: CGPoint(x: frame.origin.x + frame.size.width - insetLean, y: frame.origin.y + frame.size.height)) + borderPath.move(to: CGPoint(x: frame.origin.x + insetLean, y: frame.origin.y + frame.size.height)) + borderPath.addLine(to: CGPoint(x: frame.origin.x + insetLean, y: frame.origin.y + insetLean)) + borderPath.addLine(to: CGPoint(x: frame.origin.x + frame.size.width - insetLean, y: frame.origin.y + insetLean)) + borderPath.addLine(to: CGPoint(x: frame.origin.x + frame.size.width - insetLean, y: frame.origin.y + frame.size.height)) borderStrokeColor.setStroke() - - borderPath?.stroke() + borderPath.stroke() } } @@ -287,15 +252,15 @@ import UIKit open override func setLeftPinConstant(_ constant: CGFloat) { - textContainerLeading?.constant = constant - errorLabelLeading?.constant = constant + fieldContainerLeading?.constant = constant + feedbackLabelLeading?.constant = constant descriptionLabelLeading?.constant = constant } open override func setRightPinConstant(_ constant: CGFloat) { - textContainerTrailing?.constant = constant - errorLabelTrailing?.constant = constant + fieldContainerTrailing?.constant = constant + feedbackLabelTrailing?.constant = constant descriptionLabelTrailing?.constant = constant } @@ -303,64 +268,82 @@ import UIKit // MARK: - Form Appearance //-------------------------------------------------- - public enum Appearance { + public enum Appearance: String { case original case error - case locked - case selected + case lock + case select + case disabled } - /// Updates the visual appearance of the entry field. - public func fieldAppearance(_ state: Appearance) { + open func originalAppearance() { - switch state { - case .original: - isUserInteractionEnabled = true - borderStrokeColor = .mfSilver() - bottomBar?.backgroundColor = .black - bottomBarHeightConstraint?.constant = 1 - - case .error: - isUserInteractionEnabled = true - bottomBarHeightConstraint?.constant = 4 - - case .locked: - isUserInteractionEnabled = false - hideBorder = true - bottomBar?.backgroundColor = .clear - bottomBarHeightConstraint?.constant = 1 - - case .selected: - isUserInteractionEnabled = true - borderStrokeColor = .mfSilver() - bottomBar?.backgroundColor = .black - bottomBarHeightConstraint?.constant = 1 + isUserInteractionEnabled = true + bottomBarHeightConstraint?.constant = 1 + hideBorder = false + showErrorMessage = false + borderStrokeColor = .mfSilver() + bottomBar?.backgroundColor = .black + descriptionLabel?.textColor = .mfBattleshipGrey() + + refreshUI() + } + + open func errorAppearance(showError: Bool = false) { + + isUserInteractionEnabled = true + hideBorder = false + bottomBarHeightConstraint?.constant = 4 + borderStrokeColor = .mfPumpkin() + bottomBar?.backgroundColor = .mfPumpkin() + + if showError { + showErrorMessage = true + feedback = errorMessage } - fieldContainer?.setNeedsDisplay() + refreshUI() + } + + open func lockAppearance() { + + isUserInteractionEnabled = false + bottomBarHeightConstraint?.constant = 1 + hideBorder = true + bottomBar?.backgroundColor = .clear + + refreshUI() + } + + open func selectedAppearance() { + + isUserInteractionEnabled = true + bottomBarHeightConstraint?.constant = 1 + hideBorder = false + borderStrokeColor = .black + bottomBar?.backgroundColor = .black + + refreshUI() + } + + open func disabledAppearance() { + + isUserInteractionEnabled = false + bottomBarHeightConstraint?.constant = 1 + hideBorder = false + feedback = nil + descriptionLabel?.textColor = self.isEnabled ? UIColor.mfBattleshipGrey() : UIColor.mfSilver() + bottomBar?.backgroundColor = self.isEnabled ? (self.showErrorMessage ? UIColor.mfPumpkin() : .black) : UIColor.mfSilver() + + refreshUI() + } + + open func refreshUI() { + + self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated?(self) + setNeedsDisplay() layoutIfNeeded() } - - //-------------------------------------------------- - // MARK: - Methods - //-------------------------------------------------- - - open func showError(_ show: Bool) { - - showErrorMessage = show - - if show { - feedbackLabel?.text = errorMessage - fieldAppearance(.error) - } - } - - open func showDashSeperatorView(_ dash: Bool) { - - // Never hide seperator view because it could be possiblely used by other classes for positioning - dashLine?.isHidden = !dash - bottomBar?.backgroundColor = dash ? .clear : .black - } } // MARK: - Molecular @@ -368,6 +351,7 @@ extension FormEntryField { override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + self.delegateObject = delegateObject guard let dictionary = json, !dictionary.isEmpty @@ -389,6 +373,10 @@ extension FormEntryField { self.hideBorder = hideBorder } + if let appearance = dictionary["appearance"] as? String { + // let enu = Appearance(rawValue: appearance) + } + // Key used to send text value to server if let fieldKey = dictionary[KeyFieldKey] as? String { self.fieldKey = fieldKey @@ -396,7 +384,7 @@ extension FormEntryField { } override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return 76 + return 115 } } diff --git a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift index 0eb61015..c95da2b9 100644 --- a/MVMCoreUI/Atoms/TextFields/TextEntryField.swift +++ b/MVMCoreUI/Atoms/TextFields/TextEntryField.swift @@ -25,14 +25,6 @@ import UIKit //-------------------------------------------------- private(set) var textField: UITextField? - private var calendar: Calendar? - - //-------------------------------------------------- - // MARK: - Accessories - //-------------------------------------------------- - - public weak var datePicker: UIDatePicker? - public var pickerView: UIPickerView? //-------------------------------------------------- // MARK: - Delegate Properties @@ -173,8 +165,7 @@ import UIKit } deinit { - mfTextFieldDelegate = nil - uiTextFieldDelegate = nil + setBothTextDelegates(nil) } //-------------------------------------------------- @@ -183,7 +174,7 @@ import UIKit open func clearError() { - feedback = nil + // feedback = nil textField?.accessibilityValue = nil } @@ -245,6 +236,7 @@ import UIKit func startEditing() { + errorAppearance(showError: true) textField?.becomeFirstResponder() } } diff --git a/MVMCoreUI/Atoms/Views/DashLine.swift b/MVMCoreUI/Atoms/Views/DashLine.swift index 6197afe5..9166ddfd 100644 --- a/MVMCoreUI/Atoms/Views/DashLine.swift +++ b/MVMCoreUI/Atoms/Views/DashLine.swift @@ -25,12 +25,13 @@ open class DashLine: MFView { super.init(frame: .zero) } - public init() { - super.init(frame: .zero) + public convenience init() { + self.init(frame: .zero) } required public init?(coder: NSCoder) { super.init(coder: coder) + fatalError("DashLine xib not supported") } //------------------------------------------------------