From 1d5b631295528f7801c36f391aa7f0c4e04be040 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 20 May 2024 11:45:33 -0500 Subject: [PATCH 1/8] fixed issue with the dropdown not showing Signed-off-by: Matt Bruce --- VDS/Components/DropdownSelect/DropdownSelect.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/VDS/Components/DropdownSelect/DropdownSelect.swift b/VDS/Components/DropdownSelect/DropdownSelect.swift index 9c30ea03..944171f1 100644 --- a/VDS/Components/DropdownSelect/DropdownSelect.swift +++ b/VDS/Components/DropdownSelect/DropdownSelect.swift @@ -63,9 +63,6 @@ open class DropdownSelect: EntryFieldBase { /// Array of options to show open var options: [DropdownOptionModel] = [] { didSet { setNeedsUpdate() }} - /// A callback when the selected option changes. Passes parameters (option). - open var onItemSelected: ((Int, DropdownOptionModel) -> Void)? - //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- @@ -177,6 +174,7 @@ open class DropdownSelect: EntryFieldBase { fieldStackView .publisher(for: UITapGestureRecognizer()) .sink { [weak self] _ in + print("touch occured") self?.launchPicker() } .store(in: &subscribers) @@ -355,6 +353,7 @@ open class DropdownSelect: EntryFieldBase { open override var canBecomeFirstResponder: Bool { true } open override func resignFirstResponder() -> Bool { + print("resign occurred") if dropdownField.isFirstResponder { dropdownField.resignFirstResponder() } @@ -375,7 +374,6 @@ extension DropdownSelect: UIPickerViewDelegate, UIPickerViewDataSource { dropdownField.resignFirstResponder() } optionsPicker.isHidden = !optionsPicker.isHidden - setNeedsUpdate() } public func numberOfComponents(in pickerView: UIPickerView) -> Int { @@ -396,6 +394,5 @@ extension DropdownSelect: UIPickerViewDelegate, UIPickerViewDataSource { selectId = row updateSelectedOptionLabel(option: options[row]) sendActions(for: .valueChanged) - self.onItemSelected?(row, options[row]) } } From 9d19464b57be978d652fe9d98cb9977b01427c17 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 20 May 2024 12:17:25 -0500 Subject: [PATCH 2/8] fixed issues with dropdown / inputField Signed-off-by: Matt Bruce --- VDS/Components/DropdownSelect/DropdownSelect.swift | 14 ++------------ VDS/Components/TextFields/EntryFieldBase.swift | 2 +- .../TextFields/InputField/InputField.swift | 13 +------------ 3 files changed, 4 insertions(+), 25 deletions(-) diff --git a/VDS/Components/DropdownSelect/DropdownSelect.swift b/VDS/Components/DropdownSelect/DropdownSelect.swift index 944171f1..e2217f04 100644 --- a/VDS/Components/DropdownSelect/DropdownSelect.swift +++ b/VDS/Components/DropdownSelect/DropdownSelect.swift @@ -171,10 +171,9 @@ open class DropdownSelect: EntryFieldBase { }() // tap gesture - fieldStackView + containerView .publisher(for: UITapGestureRecognizer()) .sink { [weak self] _ in - print("touch occured") self?.launchPicker() } .store(in: &subscribers) @@ -349,16 +348,6 @@ open class DropdownSelect: EntryFieldBase { titleLabelWidthConstraint?.constant = containerView.frame.width titleLabelWidthConstraint?.isActive = helperTextPlacement == .right } - - open override var canBecomeFirstResponder: Bool { true } - - open override func resignFirstResponder() -> Bool { - print("resign occurred") - if dropdownField.isFirstResponder { - dropdownField.resignFirstResponder() - } - return super.resignFirstResponder() - } } //-------------------------------------------------- @@ -374,6 +363,7 @@ extension DropdownSelect: UIPickerViewDelegate, UIPickerViewDataSource { dropdownField.resignFirstResponder() } optionsPicker.isHidden = !optionsPicker.isHidden + updateContainerView() } public func numberOfComponents(in pickerView: UIPickerView) -> Int { diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index c7cd766c..f766fafd 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -307,7 +307,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- - private func updateContainerView() { + internal func updateContainerView() { containerView.backgroundColor = backgroundColorConfiguration.getColor(self) containerView.layer.borderColor = isReadOnly ? readOnlyBorderColorConfiguration.getColor(self).cgColor : borderColorConfiguration.getColor(self).cgColor containerView.layer.borderWidth = VDSFormControls.borderWidth diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index 1886a9ab..fbb57f2b 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -323,21 +323,12 @@ open class InputField: EntryFieldBase { titleLabelWidthConstraint?.constant = containerView.frame.width titleLabelWidthConstraint?.isActive = helperTextPlacement == .right } - - open override var canBecomeFirstResponder: Bool { true } - - open override func resignFirstResponder() -> Bool { - if textField.isFirstResponder { - textField.resignFirstResponder() - } - return super.resignFirstResponder() - } } extension InputField: UITextFieldDelegate { public func textFieldDidBeginEditing(_ textField: UITextField) { fieldType.handler().textFieldDidBeginEditing(self, textField: textField) - setNeedsUpdate() + updateContainerView() } public func textFieldDidEndEditing(_ textField: UITextField) { @@ -349,8 +340,6 @@ extension InputField: UITextFieldDelegate { fieldType.handler().textFieldDidChangeSelection(self, textField: textField) if fieldType.handler().validateOnChange { validate() - } else { - setNeedsUpdate() } sendActions(for: .valueChanged) } From 675fbf8693835f11f425e5ecc07d65d12ac991d6 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 21 May 2024 09:44:23 -0500 Subject: [PATCH 3/8] fixed keyboard issue Signed-off-by: Matt Bruce --- .../DropdownSelect/DropdownSelect.swift | 37 ++++++++++++++----- .../TextFields/InputField/TextField.swift | 22 +++++------ .../TextFields/TextArea/TextView.swift | 24 ++++++------ 3 files changed, 49 insertions(+), 34 deletions(-) diff --git a/VDS/Components/DropdownSelect/DropdownSelect.swift b/VDS/Components/DropdownSelect/DropdownSelect.swift index e2217f04..a4a7c741 100644 --- a/VDS/Components/DropdownSelect/DropdownSelect.swift +++ b/VDS/Components/DropdownSelect/DropdownSelect.swift @@ -158,16 +158,17 @@ open class DropdownSelect: EntryFieldBase { optionsPicker.isHidden = true dropdownField.inputView = optionsPicker dropdownField.inputAccessoryView = { - let inputToolbar = UIToolbar().with { - $0.barStyle = .default - $0.isTranslucent = true - $0.items=[ - UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: self, action: nil), - UIBarButtonItem(title: "Done", style: UIBarButtonItem.Style.done, target: self, action: #selector(pickerDoneClicked)) - ] - } - inputToolbar.sizeToFit() - return inputToolbar + let accessView = UIView(frame: .init(origin: .zero, size: .init(width: UIScreen.main.bounds.width, height: 44))) + accessView.backgroundColor = .white + accessView.addBorder(side: .top, width: 1, color: .lightGray) + let done = UIButton(type: .system) + done.setTitle("Done", for: .normal) + done.translatesAutoresizingMaskIntoConstraints = false + done.addTarget(self, action: #selector(pickerDoneClicked), for: .touchUpInside) + accessView.addSubview(done) + done.pinCenterY() + .pinTrailing(16) + return accessView }() // tap gesture @@ -348,6 +349,22 @@ open class DropdownSelect: EntryFieldBase { titleLabelWidthConstraint?.constant = containerView.frame.width titleLabelWidthConstraint?.isActive = helperTextPlacement == .right } + + open override var canBecomeFirstResponder: Bool { + return dropdownField.canBecomeFirstResponder + } + + open override func becomeFirstResponder() -> Bool { + return dropdownField.becomeFirstResponder() + } + + open override var canResignFirstResponder: Bool { + return dropdownField.canResignFirstResponder + } + + open override func resignFirstResponder() -> Bool { + return dropdownField.resignFirstResponder() + } } //-------------------------------------------------- diff --git a/VDS/Components/TextFields/InputField/TextField.swift b/VDS/Components/TextFields/InputField/TextField.swift index 933b8f47..182c1660 100644 --- a/VDS/Components/TextFields/InputField/TextField.swift +++ b/VDS/Components/TextFields/InputField/TextField.swift @@ -55,17 +55,17 @@ open class TextField: UITextField { } open func initialSetup() { - let doneToolbar: UIToolbar = UIToolbar() - doneToolbar.translatesAutoresizingMaskIntoConstraints = false - doneToolbar.barStyle = .default - - let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) - let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self.doneButtonAction)) - done.accessibilityHint = "Double tap to finish editing." - doneToolbar.items = [flexSpace, done] - doneToolbar.sizeToFit() - - inputAccessoryView = doneToolbar + let accessView = UIView(frame: .init(origin: .zero, size: .init(width: UIScreen.main.bounds.width, height: 44))) + accessView.backgroundColor = .white + accessView.addBorder(side: .top, width: 1, color: .lightGray) + let done = UIButton(type: .system) + done.setTitle("Done", for: .normal) + done.translatesAutoresizingMaskIntoConstraints = false + done.addTarget(self, action: #selector(doneButtonAction), for: .touchUpInside) + accessView.addSubview(done) + done.pinCenterY() + .pinTrailing(16) + inputAccessoryView = accessView } @objc func doneButtonAction() { diff --git a/VDS/Components/TextFields/TextArea/TextView.swift b/VDS/Components/TextFields/TextArea/TextView.swift index efe8164b..b1faae53 100644 --- a/VDS/Components/TextFields/TextArea/TextView.swift +++ b/VDS/Components/TextFields/TextArea/TextView.swift @@ -104,19 +104,17 @@ open class TextView: UITextView, ViewProtocol { open func setup() { - translatesAutoresizingMaskIntoConstraints = false - let doneToolbar: UIToolbar = UIToolbar() - doneToolbar.translatesAutoresizingMaskIntoConstraints = false - doneToolbar.barStyle = .default - - let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) - let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self.doneButtonAction)) - done.accessibilityHint = "Double tap to finish editing." - doneToolbar.items = [flexSpace, done] - doneToolbar.sizeToFit() - - inputAccessoryView = doneToolbar - + let accessView = UIView(frame: .init(origin: .zero, size: .init(width: UIScreen.main.bounds.width, height: 44))) + accessView.backgroundColor = .white + accessView.addBorder(side: .top, width: 1, color: .lightGray) + let done = UIButton(type: .system) + done.setTitle("Done", for: .normal) + done.translatesAutoresizingMaskIntoConstraints = false + done.addTarget(self, action: #selector(doneButtonAction), for: .touchUpInside) + accessView.addSubview(done) + done.pinCenterY() + .pinTrailing(16) + inputAccessoryView = accessView } @objc func doneButtonAction() { From a4d9fd0ccc0b05393601397a6f2ac3aa0963993b Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 21 May 2024 09:49:56 -0500 Subject: [PATCH 4/8] updated responder methods Signed-off-by: Matt Bruce --- .../TextFields/InputField/InputField.swift | 16 ++++++++++++++++ .../TextFields/TextArea/TextArea.swift | 19 +++++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index fbb57f2b..60adf47a 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -323,6 +323,22 @@ open class InputField: EntryFieldBase { titleLabelWidthConstraint?.constant = containerView.frame.width titleLabelWidthConstraint?.isActive = helperTextPlacement == .right } + + open override var canBecomeFirstResponder: Bool { + return textField.canBecomeFirstResponder + } + + open override func becomeFirstResponder() -> Bool { + return textField.becomeFirstResponder() + } + + open override var canResignFirstResponder: Bool { + return textField.canResignFirstResponder + } + + open override func resignFirstResponder() -> Bool { + return textField.resignFirstResponder() + } } extension InputField: UITextFieldDelegate { diff --git a/VDS/Components/TextFields/TextArea/TextArea.swift b/VDS/Components/TextFields/TextArea/TextArea.swift index eccd228e..2333a17f 100644 --- a/VDS/Components/TextFields/TextArea/TextArea.swift +++ b/VDS/Components/TextFields/TextArea/TextArea.swift @@ -253,13 +253,20 @@ open class TextArea: EntryFieldBase { } - open override var canBecomeFirstResponder: Bool { true } - + open override var canBecomeFirstResponder: Bool { + return textView.canBecomeFirstResponder + } + + open override func becomeFirstResponder() -> Bool { + return textView.becomeFirstResponder() + } + + open override var canResignFirstResponder: Bool { + return textView.canResignFirstResponder + } + open override func resignFirstResponder() -> Bool { - if textView.isFirstResponder { - textView.resignFirstResponder() - } - return super.resignFirstResponder() + return textView.resignFirstResponder() } //-------------------------------------------------- From 522624b403f1076561ae08d1e6636da6b95c371c Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 21 May 2024 14:05:37 -0500 Subject: [PATCH 5/8] initial fix Signed-off-by: Matt Bruce --- .../DropdownSelect/DropdownSelect.swift | 41 +-- .../TextFields/EntryFieldBase.swift | 273 ++++++++++-------- .../TextFields/InputField/InputField.swift | 42 +-- 3 files changed, 161 insertions(+), 195 deletions(-) diff --git a/VDS/Components/DropdownSelect/DropdownSelect.swift b/VDS/Components/DropdownSelect/DropdownSelect.swift index a4a7c741..ac5cb29b 100644 --- a/VDS/Components/DropdownSelect/DropdownSelect.swift +++ b/VDS/Components/DropdownSelect/DropdownSelect.swift @@ -111,16 +111,12 @@ open class DropdownSelect: EntryFieldBase { $0.font = TextStyle.bodyLarge.font } - /// Determines the placement of the helper text. - open var helperTextPlacement: HelperTextPlacement = .bottom { didSet { setNeedsUpdate() } } - open var optionsPicker = UIPickerView() //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- internal var inlineWidthConstraint: NSLayoutConstraint? - internal var titleLabelWidthConstraint: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Configuration Properties @@ -133,13 +129,8 @@ open class DropdownSelect: EntryFieldBase { /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. open override func setup() { - super.setup() - widthConstraint?.activate() + super.setup() - titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal) - titleLabel.setContentHuggingPriority(.required, for: .horizontal) - titleLabelWidthConstraint = titleLabel.width(constant: 0) - fieldStackView.isAccessibilityElement = true fieldStackView.accessibilityLabel = "Dropdown Select" inlineDisplayLabel.isAccessibilityElement = true @@ -285,28 +276,6 @@ open class DropdownSelect: EntryFieldBase { statusIcon.color = iconColorConfiguration.getColor(self) } - open override func updateHelperLabel(){ - //remove first - secondaryStackView.removeFromSuperview() - helperLabel.removeFromSuperview() - - super.updateHelperLabel() - - //set the helper label position - if helperText != nil { - if helperTextPlacement == .right { - horizontalStackView.addArrangedSubview(secondaryStackView) - horizontalStackView.addArrangedSubview(helperLabel) - primaryStackView.addArrangedSubview(horizontalStackView) - } else { - bottomContainerStackView.addArrangedSubview(helperLabel) - primaryStackView.addArrangedSubview(secondaryStackView) - } - } else { - primaryStackView.addArrangedSubview(secondaryStackView) - } - } - open override func updateAccessibility() { super.updateAccessibility() let selectedOption = selectedOptionLabel.text ?? "" @@ -343,13 +312,7 @@ open class DropdownSelect: EntryFieldBase { setNeedsUpdate() UIAccessibility.post(notification: .layoutChanged, argument: fieldStackView) } - - open override func layoutSubviews() { - super.layoutSubviews() - titleLabelWidthConstraint?.constant = containerView.frame.width - titleLabelWidthConstraint?.isActive = helperTextPlacement == .right - } - + open override var canBecomeFirstResponder: Bool { return dropdownField.canBecomeFirstResponder } diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index f766fafd..631cf029 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -36,38 +36,41 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { public enum HelperTextPlacement: String, CaseIterable { case bottom, right } - + //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - internal var primaryStackView: UIStackView = { - return UIStackView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.axis = .vertical - $0.distribution = .fill - $0.alignment = .leading - } - }() - - /// This is the veritcal stack view that has 2 rows, the containerView and the return view - /// of the getBottomContainer() method, by default returns the bottomContainerStackView. - internal let secondaryStackView = UIStackView().with { - $0.translatesAutoresizingMaskIntoConstraints = false + internal let mainStackView = UIStackView().with { $0.axis = .vertical - $0.distribution = .fill + $0.alignment = .fill + $0.spacing = 8 + $0.translatesAutoresizingMaskIntoConstraints = false } - /// This is the view that will be wrapped with the border for userInteraction. - /// The only subview of this view is the fieldStackView - internal var containerView: UIView = { - return UIView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - } - }() + internal let contentStackView = UIStackView().with { + $0.axis = .vertical + $0.alignment = .fill + $0.distribution = .fill + $0.spacing = 8 + $0.translatesAutoresizingMaskIntoConstraints = false + } + + /// only used for helperTextPosition == .right + internal let row1StackView = UIStackView().with { + $0.axis = .horizontal + $0.spacing = 8 + $0.alignment = .top + $0.distribution = .fillEqually + } + + /// only used for helperTextPosition == .right + internal let row2StackView = UIStackView().with { + $0.axis = .horizontal + $0.spacing = 8 + $0.alignment = .top + $0.distribution = .fillEqually + } - /// This is a horizontal Stack View that is placed inside the containterView (bordered view) - /// The first arrangedView will be the view from getFieldContainer() - /// The second view is the statusIcon. internal var fieldStackView: UIStackView = { return UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false @@ -86,9 +89,18 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { $0.spacing = VDSLayout.space2X } }() + + /// This is the view that will be wrapped with the border for userInteraction. + /// The only subview of this view is the fieldStackView + internal var containerView: UIView = { + return UIView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + } + }() + + /// This is set by a local method. + internal var bottomContainerView: UIView! - open var rules = [AnyRule]() - //-------------------------------------------------- // MARK: - Configuration Properties //-------------------------------------------------- @@ -136,7 +148,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- - open var onChangeSubscriber: AnyCancellable? + open var onChangeSubscriber: AnyCancellable? open var titleLabel = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) @@ -199,36 +211,34 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { open var isRequired: Bool = false { didSet { setNeedsUpdate() } } open var isReadOnly: Bool = false { didSet { setNeedsUpdate() } } - - //-------------------------------------------------- - // MARK: - Constraints - //-------------------------------------------------- - internal var heightConstraint: NSLayoutConstraint? - internal var widthConstraint: NSLayoutConstraint? - + + open var helperTextPlacement: HelperTextPlacement = .bottom { + didSet { + updateHelperTextPosition() + } + } + + open var rules = [AnyRule]() + //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- + /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. open override func setup() { super.setup() - - isAccessibilityElement = false - addSubview(primaryStackView) - //create the wrapping view - heightConstraint = containerView.heightGreaterThanEqualTo(constant: containerSize.height) - widthConstraint = containerView.width(constant: frame.size.width) - - secondaryStackView.addArrangedSubview(containerView) - secondaryStackView.setCustomSpacing(8, after: containerView) + // Add mainStackView to the view + addSubview(mainStackView) + + mainStackView.pinToSuperView() //add ContainerStackView //this is the horizontal stack that contains - //the left, InputContainer, Icons, Buttons + //InputContainer, Icons, Buttons containerView.addSubview(fieldStackView) fieldStackView.pinToSuperView(.uniform(VDSLayout.space3X)) - + let fieldContainerView = getFieldContainer() fieldContainerView.translatesAutoresizingMaskIntoConstraints = false @@ -239,29 +249,33 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { //get the container this is what show helper text, error text //can include other for character count, max length - let bottomContainer = getBottomContainer() + bottomContainerView = getBottomContainer() //this is the vertical stack that contains error text, helper text bottomContainerStackView.addArrangedSubview(errorLabel) bottomContainerStackView.addArrangedSubview(helperLabel) - primaryStackView.addArrangedSubview(titleLabel) - primaryStackView.addArrangedSubview(secondaryStackView) - secondaryStackView.addArrangedSubview(bottomContainer) - - primaryStackView.setCustomSpacing(4, after: titleLabel) - - primaryStackView - .pinTop() - .pinLeading() - .pinTrailing(0, .defaultHigh) - .pinBottom(0, .defaultHigh) - - titleLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() - errorLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() - helperLabel.textColorConfiguration = secondaryColorConfiguration.eraseToAnyColorable() + // Add arranged subviews to textFieldStackView + contentStackView.addArrangedSubview(containerView) + contentStackView.addArrangedSubview(bottomContainerView) + + // Add arranged subviews to mainStackView + mainStackView.addArrangedSubview(titleLabel) + mainStackView.addArrangedSubview(contentStackView) + + // Initial position of the helper label + updateHelperTextPosition() } - + + /// Updates the UI + open override func updateView() { + super.updateView() + updateContainerView() + updateTitleLabel() + updateErrorLabel() + updateHelperLabel() + } + /// Resets to default settings. open override func reset() { super.reset() @@ -284,48 +298,12 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { defaultValue = nil isRequired = false isReadOnly = false - onChange = nil + onChange = nil } - - /// Used to make changes to the View based off a change events or from local properties. - open override func updateView() { - super.updateView() - updateContainerView() - updateTitleLabel() - updateErrorLabel() - updateHelperLabel() - updateContainerWidth() - } - - open func validate(){ - updateRules() - validator = FormFieldValidator(field: self, rules: rules) - validator?.validate() - setNeedsUpdate() - } - - //-------------------------------------------------- - // MARK: - Private Methods - //-------------------------------------------------- - internal func updateContainerView() { - containerView.backgroundColor = backgroundColorConfiguration.getColor(self) - containerView.layer.borderColor = isReadOnly ? readOnlyBorderColorConfiguration.getColor(self).cgColor : borderColorConfiguration.getColor(self).cgColor - containerView.layer.borderWidth = VDSFormControls.borderWidth - containerView.layer.cornerRadius = VDSFormControls.borderRadius - } - + //-------------------------------------------------- // MARK: - Public Methods //-------------------------------------------------- - open func updateContainerWidth() { - if let width, width > minWidth && width < maxWidth { - widthConstraint?.constant = width - } else { - widthConstraint?.constant = maxWidth >= minWidth ? maxWidth : minWidth - } - widthConstraint?.activate() - } - /// Container for the area in which the user interacts. open func getFieldContainer() -> UIView { fatalError("Subclass must return the view that contains the field/view the user will interact with.") @@ -335,22 +313,14 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { open func getBottomContainer() -> UIView { return bottomContainerStackView } - - internal func updateRules() { - rules.removeAll() - if self.isRequired { - let rule = RequiredRule() - if let errorText, !errorText.isEmpty { - rule.errorMessage = errorText - } else if let labelText{ - rule.errorMessage = "You must enter a \(labelText)" - } else { - rule.errorMessage = "You must enter a value" - } - rules.append(.init(rule)) - } + + open func validate(){ + updateRules() + validator = FormFieldValidator(field: self, rules: rules) + validator?.validate() + setNeedsUpdate() } - + open func updateTitleLabel() { //update the local vars for the label since we no @@ -380,7 +350,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { titleLabel.surface = surface titleLabel.isEnabled = isEnabled } - + open func updateErrorLabel(){ if showError, let errorText { errorLabel.text = errorText @@ -416,4 +386,73 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { helperLabel.isHidden = true } } + + //-------------------------------------------------- + // MARK: - Private Methods + //-------------------------------------------------- + internal func updateRules() { + rules.removeAll() + if self.isRequired { + let rule = RequiredRule() + if let errorText, !errorText.isEmpty { + rule.errorMessage = errorText + } else if let labelText{ + rule.errorMessage = "You must enter a \(labelText)" + } else { + rule.errorMessage = "You must enter a value" + } + rules.append(.init(rule)) + } + } + + internal func updateContainerView() { + containerView.backgroundColor = backgroundColorConfiguration.getColor(self) + containerView.layer.borderColor = borderColorConfiguration.getColor(self).cgColor + containerView.layer.borderWidth = VDSFormControls.borderWidth + containerView.layer.cornerRadius = VDSFormControls.borderRadius + } + + internal func updateHelperTextPosition() { + + titleLabel.removeFromSuperview() + helperLabel.removeFromSuperview() + + contentStackView.removeFromSuperview() + mainStackView.removeArrangedSubviews() + + //rows for helper-right + row1StackView.removeArrangedSubviews() + row2StackView.removeArrangedSubviews() + row1StackView.removeFromSuperview() + row2StackView.removeFromSuperview() + + switch helperTextPlacement { + case .bottom: + //add helper back into the contentView + bottomContainerStackView.addArrangedSubview(helperLabel) + mainStackView.addArrangedSubview(titleLabel) + mainStackView.addArrangedSubview(contentStackView) + + case .right: + //first row + row1StackView.addArrangedSubview(titleLabel) + //add spacer + row1StackView.addArrangedSubview(UIView()) + + //second row + row2StackView.addArrangedSubview(contentStackView) + //add under spacer + row2StackView.addArrangedSubview(helperLabel) + + //add 2 rows to vertical stack to create the grid + mainStackView.addArrangedSubview(row1StackView) + mainStackView.addArrangedSubview(row2StackView) + } + } +} + +extension UIStackView { + public func removeArrangedSubviews() { + arrangedSubviews.forEach { removeArrangedSubview($0) } + } } diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index 60adf47a..737404d4 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -34,7 +34,6 @@ open class InputField: EntryFieldBase { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - internal var titleLabelWidthConstraint: NSLayoutConstraint? internal override var minWidth: CGFloat { fieldType.handler().minWidth } internal override var maxWidth: CGFloat { let frameWidth = frame.size.width @@ -172,25 +171,17 @@ open class InputField: EntryFieldBase { /// If given, this will be shown if showSuccess if true. open var successText: String? { didSet { setNeedsUpdate() } } - - /// Determines the placement of the helper text. - open var helperTextPlacement: HelperTextPlacement = .bottom { didSet { setNeedsUpdate() } } //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. open override func setup() { - super.setup() - - titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal) - titleLabel.setContentHuggingPriority(.required, for: .horizontal) - titleLabelWidthConstraint = titleLabel.width(constant: 0) - + super.setup() textField.heightAnchor.constraint(equalToConstant: 20).isActive = true textField.delegate = self - primaryStackView.addArrangedSubview(successLabel) - primaryStackView.setCustomSpacing(8, after: successLabel) + mainStackView.addArrangedSubview(successLabel) + mainStackView.setCustomSpacing(8, after: successLabel) fieldStackView.addArrangedSubview(actionTextLink) @@ -262,27 +253,6 @@ open class InputField: EntryFieldBase { } } - open override func updateHelperLabel(){ - //remove first - secondaryStackView.removeFromSuperview() - helperLabel.removeFromSuperview() - - super.updateHelperLabel() - - //set the helper label position - if helperText != nil { - if helperTextPlacement == .right { - horizontalStackView.addArrangedSubview(secondaryStackView) - horizontalStackView.addArrangedSubview(helperLabel) - primaryStackView.addArrangedSubview(horizontalStackView) - } else { - bottomContainerStackView.addArrangedSubview(helperLabel) - primaryStackView.addArrangedSubview(secondaryStackView) - } - } else { - primaryStackView.addArrangedSubview(secondaryStackView) - } - } override func updateRules() { super.updateRules() @@ -318,12 +288,6 @@ open class InputField: EntryFieldBase { set { super.accessibilityElements = newValue } } - open override func layoutSubviews() { - super.layoutSubviews() - titleLabelWidthConstraint?.constant = containerView.frame.width - titleLabelWidthConstraint?.isActive = helperTextPlacement == .right - } - open override var canBecomeFirstResponder: Bool { return textField.canBecomeFirstResponder } From e70d160619bf3182932a22e9e5264bdefbf49827 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 21 May 2024 14:44:20 -0500 Subject: [PATCH 6/8] more refactoring Signed-off-by: Matt Bruce --- .../TextFields/InputField/InputField.swift | 27 ++++--------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index 737404d4..70842973 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -97,16 +97,7 @@ open class InputField: EntryFieldBase { $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true) $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forDisabled: false) }.eraseToAnyColorable() - - open var leftImageView = UIImageView().with { - $0.height(20) - $0.width(32) - $0.isAccessibilityElement = false - $0.translatesAutoresizingMaskIntoConstraints = false - $0.contentMode = .scaleAspectFill - $0.clipsToBounds = true - } - + open var actionTextLink = TextLink().with { $0.contentEdgeInsets = .top(-2) } open var actionTextLinkModel: TextLinkModel? { didSet { setNeedsUpdate() } } @@ -177,11 +168,10 @@ open class InputField: EntryFieldBase { //-------------------------------------------------- /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. open override func setup() { - super.setup() + super.setup() textField.heightAnchor.constraint(equalToConstant: 20).isActive = true textField.delegate = self - mainStackView.addArrangedSubview(successLabel) - mainStackView.setCustomSpacing(8, after: successLabel) + bottomContainerStackView.insertArrangedSubview(successLabel, at: 0) fieldStackView.addArrangedSubview(actionTextLink) @@ -194,15 +184,7 @@ open class InputField: EntryFieldBase { } open override func getFieldContainer() -> UIView { - // stackview for controls in EntryFieldBase.controlContainerView - let stackView = UIStackView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.axis = .horizontal - $0.spacing = VDSLayout.space3X - } - stackView.addArrangedSubview(leftImageView) - stackView.addArrangedSubview(textField) - return stackView + return textField } /// Resets to default settings. @@ -322,6 +304,7 @@ extension InputField: UITextFieldDelegate { validate() } sendActions(for: .valueChanged) + setNeedsUpdate() } public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { From 3bc2b18962188ee3b37b6e80a96c5872af8632b7 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 21 May 2024 14:44:44 -0500 Subject: [PATCH 7/8] updated field types Signed-off-by: Matt Bruce --- .../InputField/FieldTypes/CreditCard.swift | 45 ++++++++++++++----- .../InputField/FieldTypes/FieldType.swift | 12 ++--- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift b/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift index 7137d895..43fd6309 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift @@ -20,7 +20,7 @@ extension InputField { case jcb case unionPay - func imageName(surface: Surface) -> String { + public func imageName(surface: Surface) -> String { func getImageName(_ surface: Surface, name: String) -> String { return surface == .light ? name : "\(name)-inverted" } @@ -98,12 +98,35 @@ extension InputField { self.keyboardType = .numberPad } - override func updateView(_ inputField: InputField) { - minWidth = 288.0 - leftImageName = inputField.cardType.imageName(surface: inputField.surface) - super.updateView(inputField) + fileprivate func updateLeftImage(_ inputField: InputField) { + let imageName = inputField.cardType.imageName(surface: inputField.surface) + creditCardImageView.image = BundleManager.shared.image(for: imageName) } + override func updateView(_ inputField: InputField) { + minWidth = 288.0 + super.updateView(inputField) + + // Set the UIImageView as the left view of the UITextField + let iconContainerView: UIView = UIView() + iconContainerView.addSubview(creditCardImageView) + creditCardImageView.pinToSuperView(.init(top: 0, left: 0, bottom: 0, right: 10)) + + inputField.textField.leftView = iconContainerView + inputField.textField.leftViewMode = .always + + updateLeftImage(inputField) + } + + internal var creditCardImageView = UIImageView().with { + $0.height(20) + $0.width(32) + $0.isAccessibilityElement = false + $0.translatesAutoresizingMaskIntoConstraints = false + $0.contentMode = .scaleAspectFill + $0.clipsToBounds = true + } + override func appendRules(_ inputField: InputField) { if let text = inputField.textField.text, text.count > 0 { let rule = CharacterCountRule().copyWith { @@ -120,6 +143,8 @@ extension InputField { value = nil inputField.cardType = .generic textField.text = "" + inputField.validate() + updateLeftImage(inputField) } override func textFieldDidEndEditing(_ inputField: InputField, textField: UITextField) { @@ -183,14 +208,12 @@ extension InputField { } internal func updateCardTypeIcon(_ inputField: InputField, rawNumber: String) { - defer { inputField.setNeedsUpdate() } - - guard rawNumber.count >= 4 else { + if rawNumber.count >= 4 { + inputField.cardType = CreditCardType.from(cardNumber: rawNumber) + } else { inputField.cardType = .generic - return } - - inputField.cardType = CreditCardType.from(cardNumber: rawNumber) + updateLeftImage(inputField) } internal func maskCreditCardNumber(_ cardType: CreditCardType, number: String) -> String { diff --git a/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift b/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift index ebecb17d..f16f8c51 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift @@ -39,7 +39,6 @@ extension InputField { class FieldTypeHandler: NSObject { var keyboardType: UIKeyboardType var minWidth: CGFloat = 40.0 - var leftImageName: String? var actionModel: TextLinkModel? var toolTipModel: Tooltip.TooltipModel? var isSecureTextEntry = false @@ -51,7 +50,7 @@ extension InputField { keyboardType = .default super.init() } - + func updateView(_ inputField: InputField) { //keyboard @@ -59,15 +58,10 @@ extension InputField { //textField inputField.textField.isSecureTextEntry = isSecureTextEntry - - //leftIcon - if let leftImageName { - inputField.leftImageView.image = BundleManager.shared.image(for: leftImageName) - } - inputField.leftImageView.isHidden = leftImageName == nil //actionLink inputField.actionTextLink.surface = inputField.surface + inputField.actionTextLink.isEnabled = inputField.isEnabled if let actionModel { inputField.actionTextLink.text = actionModel.text inputField.actionTextLink.onClick = { _ in @@ -88,6 +82,8 @@ extension InputField { if let toolTipModel { inputField.tooltipModel = toolTipModel } + + inputField.textField.leftView = nil } func appendRules(_ inputField: InputField) {} From dc4db4816db5049189e04050755276de60188948 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 22 May 2024 08:45:31 -0500 Subject: [PATCH 8/8] pushed out fix for using surface color config Signed-off-by: Matt Bruce --- VDS/Components/Icon/Icon.swift | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/VDS/Components/Icon/Icon.swift b/VDS/Components/Icon/Icon.swift index 09c1ffec..e5069f8f 100644 --- a/VDS/Components/Icon/Icon.swift +++ b/VDS/Components/Icon/Icon.swift @@ -56,10 +56,12 @@ open class Icon: View { if let hex = color.hexString, !UIColor.isVDSColor(color: color) { print("icon.color is not a VDSColor. Hex: \(hex) is not a supported color") } - setNeedsUpdate() + colorConfiguration = SurfaceColorConfiguration(color, color) } } + open var colorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) { didSet { setNeedsUpdate() } } + /// Size of the icon. open var size: Size = .medium { didSet { setNeedsUpdate() } } @@ -98,15 +100,8 @@ open class Icon: View { open override func updateView() { super.updateView() //get the color for the image - var imageColor = color - - //ensure the correct color for white/black colors - if surface == .dark && color == VDSColor.paletteBlack { - imageColor = VDSColor.elementsPrimaryOndark - } else if surface == .light && color == VDSColor.paletteBlack { - imageColor = VDSColor.elementsPrimaryOnlight - } - + let imageColor = colorConfiguration.getColor(surface) + //get the image name //set the image if let name, let image = UIImage.image(for: name, color: imageColor) {