initial fix

Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
This commit is contained in:
Matt Bruce 2024-05-21 14:05:37 -05:00
parent a4d9fd0ccc
commit 522624b403
3 changed files with 161 additions and 195 deletions

View File

@ -111,16 +111,12 @@ open class DropdownSelect: EntryFieldBase {
$0.font = TextStyle.bodyLarge.font $0.font = TextStyle.bodyLarge.font
} }
/// Determines the placement of the helper text.
open var helperTextPlacement: HelperTextPlacement = .bottom { didSet { setNeedsUpdate() } }
open var optionsPicker = UIPickerView() open var optionsPicker = UIPickerView()
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Constraints // MARK: - Constraints
//-------------------------------------------------- //--------------------------------------------------
internal var inlineWidthConstraint: NSLayoutConstraint? internal var inlineWidthConstraint: NSLayoutConstraint?
internal var titleLabelWidthConstraint: NSLayoutConstraint?
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Configuration Properties // 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. /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
open override func setup() { open override func setup() {
super.setup() super.setup()
widthConstraint?.activate()
titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
titleLabel.setContentHuggingPriority(.required, for: .horizontal)
titleLabelWidthConstraint = titleLabel.width(constant: 0)
fieldStackView.isAccessibilityElement = true fieldStackView.isAccessibilityElement = true
fieldStackView.accessibilityLabel = "Dropdown Select" fieldStackView.accessibilityLabel = "Dropdown Select"
inlineDisplayLabel.isAccessibilityElement = true inlineDisplayLabel.isAccessibilityElement = true
@ -285,28 +276,6 @@ open class DropdownSelect: EntryFieldBase {
statusIcon.color = iconColorConfiguration.getColor(self) 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() { open override func updateAccessibility() {
super.updateAccessibility() super.updateAccessibility()
let selectedOption = selectedOptionLabel.text ?? "" let selectedOption = selectedOptionLabel.text ?? ""
@ -343,13 +312,7 @@ open class DropdownSelect: EntryFieldBase {
setNeedsUpdate() setNeedsUpdate()
UIAccessibility.post(notification: .layoutChanged, argument: fieldStackView) 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 { open override var canBecomeFirstResponder: Bool {
return dropdownField.canBecomeFirstResponder return dropdownField.canBecomeFirstResponder
} }

View File

@ -36,38 +36,41 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
public enum HelperTextPlacement: String, CaseIterable { public enum HelperTextPlacement: String, CaseIterable {
case bottom, right case bottom, right
} }
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Private Properties // MARK: - Private Properties
//-------------------------------------------------- //--------------------------------------------------
internal var primaryStackView: UIStackView = { internal let mainStackView = UIStackView().with {
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
$0.axis = .vertical $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. internal let contentStackView = UIStackView().with {
/// The only subview of this view is the fieldStackView $0.axis = .vertical
internal var containerView: UIView = { $0.alignment = .fill
return UIView().with { $0.distribution = .fill
$0.translatesAutoresizingMaskIntoConstraints = false $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 = { internal var fieldStackView: UIStackView = {
return UIStackView().with { return UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false $0.translatesAutoresizingMaskIntoConstraints = false
@ -86,9 +89,18 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
$0.spacing = VDSLayout.space2X $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<String>]()
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Configuration Properties // MARK: - Configuration Properties
//-------------------------------------------------- //--------------------------------------------------
@ -136,7 +148,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Public Properties // MARK: - Public Properties
//-------------------------------------------------- //--------------------------------------------------
open var onChangeSubscriber: AnyCancellable? open var onChangeSubscriber: AnyCancellable?
open var titleLabel = Label().with { open var titleLabel = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical) $0.setContentCompressionResistancePriority(.required, for: .vertical)
@ -199,36 +211,34 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
open var isRequired: Bool = false { didSet { setNeedsUpdate() } } open var isRequired: Bool = false { didSet { setNeedsUpdate() } }
open var isReadOnly: Bool = false { didSet { setNeedsUpdate() } } open var isReadOnly: Bool = false { didSet { setNeedsUpdate() } }
//-------------------------------------------------- open var helperTextPlacement: HelperTextPlacement = .bottom {
// MARK: - Constraints didSet {
//-------------------------------------------------- updateHelperTextPosition()
internal var heightConstraint: NSLayoutConstraint? }
internal var widthConstraint: NSLayoutConstraint? }
open var rules = [AnyRule<String>]()
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Overrides // MARK: - Overrides
//-------------------------------------------------- //--------------------------------------------------
/// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
open override func setup() { open override func setup() {
super.setup() super.setup()
isAccessibilityElement = false
addSubview(primaryStackView)
//create the wrapping view // Add mainStackView to the view
heightConstraint = containerView.heightGreaterThanEqualTo(constant: containerSize.height) addSubview(mainStackView)
widthConstraint = containerView.width(constant: frame.size.width)
mainStackView.pinToSuperView()
secondaryStackView.addArrangedSubview(containerView)
secondaryStackView.setCustomSpacing(8, after: containerView)
//add ContainerStackView //add ContainerStackView
//this is the horizontal stack that contains //this is the horizontal stack that contains
//the left, InputContainer, Icons, Buttons //InputContainer, Icons, Buttons
containerView.addSubview(fieldStackView) containerView.addSubview(fieldStackView)
fieldStackView.pinToSuperView(.uniform(VDSLayout.space3X)) fieldStackView.pinToSuperView(.uniform(VDSLayout.space3X))
let fieldContainerView = getFieldContainer() let fieldContainerView = getFieldContainer()
fieldContainerView.translatesAutoresizingMaskIntoConstraints = false fieldContainerView.translatesAutoresizingMaskIntoConstraints = false
@ -239,29 +249,33 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
//get the container this is what show helper text, error text //get the container this is what show helper text, error text
//can include other for character count, max length //can include other for character count, max length
let bottomContainer = getBottomContainer() bottomContainerView = getBottomContainer()
//this is the vertical stack that contains error text, helper text //this is the vertical stack that contains error text, helper text
bottomContainerStackView.addArrangedSubview(errorLabel) bottomContainerStackView.addArrangedSubview(errorLabel)
bottomContainerStackView.addArrangedSubview(helperLabel) bottomContainerStackView.addArrangedSubview(helperLabel)
primaryStackView.addArrangedSubview(titleLabel) // Add arranged subviews to textFieldStackView
primaryStackView.addArrangedSubview(secondaryStackView) contentStackView.addArrangedSubview(containerView)
secondaryStackView.addArrangedSubview(bottomContainer) contentStackView.addArrangedSubview(bottomContainerView)
primaryStackView.setCustomSpacing(4, after: titleLabel) // Add arranged subviews to mainStackView
mainStackView.addArrangedSubview(titleLabel)
primaryStackView mainStackView.addArrangedSubview(contentStackView)
.pinTop()
.pinLeading() // Initial position of the helper label
.pinTrailing(0, .defaultHigh) updateHelperTextPosition()
.pinBottom(0, .defaultHigh)
titleLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable()
errorLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable()
helperLabel.textColorConfiguration = secondaryColorConfiguration.eraseToAnyColorable()
} }
/// Updates the UI
open override func updateView() {
super.updateView()
updateContainerView()
updateTitleLabel()
updateErrorLabel()
updateHelperLabel()
}
/// Resets to default settings. /// Resets to default settings.
open override func reset() { open override func reset() {
super.reset() super.reset()
@ -284,48 +298,12 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
defaultValue = nil defaultValue = nil
isRequired = false isRequired = false
isReadOnly = 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<EntryFieldBase>(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 // 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. /// Container for the area in which the user interacts.
open func getFieldContainer() -> UIView { open func getFieldContainer() -> UIView {
fatalError("Subclass must return the view that contains the field/view the user will interact with.") 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 { open func getBottomContainer() -> UIView {
return bottomContainerStackView return bottomContainerStackView
} }
internal func updateRules() { open func validate(){
rules.removeAll() updateRules()
if self.isRequired { validator = FormFieldValidator<EntryFieldBase>(field: self, rules: rules)
let rule = RequiredRule() validator?.validate()
if let errorText, !errorText.isEmpty { setNeedsUpdate()
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 updateTitleLabel() { open func updateTitleLabel() {
//update the local vars for the label since we no //update the local vars for the label since we no
@ -380,7 +350,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
titleLabel.surface = surface titleLabel.surface = surface
titleLabel.isEnabled = isEnabled titleLabel.isEnabled = isEnabled
} }
open func updateErrorLabel(){ open func updateErrorLabel(){
if showError, let errorText { if showError, let errorText {
errorLabel.text = errorText errorLabel.text = errorText
@ -416,4 +386,73 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
helperLabel.isHidden = true 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) }
}
} }

View File

@ -34,7 +34,6 @@ open class InputField: EntryFieldBase {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Private Properties // MARK: - Private Properties
//-------------------------------------------------- //--------------------------------------------------
internal var titleLabelWidthConstraint: NSLayoutConstraint?
internal override var minWidth: CGFloat { fieldType.handler().minWidth } internal override var minWidth: CGFloat { fieldType.handler().minWidth }
internal override var maxWidth: CGFloat { internal override var maxWidth: CGFloat {
let frameWidth = frame.size.width let frameWidth = frame.size.width
@ -172,25 +171,17 @@ open class InputField: EntryFieldBase {
/// If given, this will be shown if showSuccess if true. /// If given, this will be shown if showSuccess if true.
open var successText: String? { didSet { setNeedsUpdate() } } open var successText: String? { didSet { setNeedsUpdate() } }
/// Determines the placement of the helper text.
open var helperTextPlacement: HelperTextPlacement = .bottom { didSet { setNeedsUpdate() } }
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Overrides // MARK: - Overrides
//-------------------------------------------------- //--------------------------------------------------
/// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
open override func setup() { open override func setup() {
super.setup() super.setup()
titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
titleLabel.setContentHuggingPriority(.required, for: .horizontal)
titleLabelWidthConstraint = titleLabel.width(constant: 0)
textField.heightAnchor.constraint(equalToConstant: 20).isActive = true textField.heightAnchor.constraint(equalToConstant: 20).isActive = true
textField.delegate = self textField.delegate = self
primaryStackView.addArrangedSubview(successLabel) mainStackView.addArrangedSubview(successLabel)
primaryStackView.setCustomSpacing(8, after: successLabel) mainStackView.setCustomSpacing(8, after: successLabel)
fieldStackView.addArrangedSubview(actionTextLink) 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() { override func updateRules() {
super.updateRules() super.updateRules()
@ -318,12 +288,6 @@ open class InputField: EntryFieldBase {
set { super.accessibilityElements = newValue } set { super.accessibilityElements = newValue }
} }
open override func layoutSubviews() {
super.layoutSubviews()
titleLabelWidthConstraint?.constant = containerView.frame.width
titleLabelWidthConstraint?.isActive = helperTextPlacement == .right
}
open override var canBecomeFirstResponder: Bool { open override var canBecomeFirstResponder: Bool {
return textField.canBecomeFirstResponder return textField.canBecomeFirstResponder
} }