Merge branch 'develop' into feature/monarch

This commit is contained in:
Matt Bruce 2024-05-22 09:39:52 -05:00
commit 80e2050dfb
9 changed files with 278 additions and 301 deletions

View File

@ -63,9 +63,6 @@ open class DropdownSelect: EntryFieldBase {
/// Array of options to show /// Array of options to show
open var options: [DropdownOptionModel] = [] { didSet { setNeedsUpdate() }} 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 // MARK: - Private Properties
//-------------------------------------------------- //--------------------------------------------------
@ -114,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
@ -137,11 +130,6 @@ 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"
@ -161,20 +149,21 @@ open class DropdownSelect: EntryFieldBase {
optionsPicker.isHidden = true optionsPicker.isHidden = true
dropdownField.inputView = optionsPicker dropdownField.inputView = optionsPicker
dropdownField.inputAccessoryView = { dropdownField.inputAccessoryView = {
let inputToolbar = UIToolbar().with { let accessView = UIView(frame: .init(origin: .zero, size: .init(width: UIScreen.main.bounds.width, height: 44)))
$0.barStyle = .default accessView.backgroundColor = .white
$0.isTranslucent = true accessView.addBorder(side: .top, width: 1, color: .lightGray)
$0.items=[ let done = UIButton(type: .system)
UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: self, action: nil), done.setTitle("Done", for: .normal)
UIBarButtonItem(title: "Done", style: UIBarButtonItem.Style.done, target: self, action: #selector(pickerDoneClicked)) done.translatesAutoresizingMaskIntoConstraints = false
] done.addTarget(self, action: #selector(pickerDoneClicked), for: .touchUpInside)
} accessView.addSubview(done)
inputToolbar.sizeToFit() done.pinCenterY()
return inputToolbar .pinTrailing(16)
return accessView
}() }()
// tap gesture // tap gesture
fieldStackView containerView
.publisher(for: UITapGestureRecognizer()) .publisher(for: UITapGestureRecognizer())
.sink { [weak self] _ in .sink { [weak self] _ in
self?.launchPicker() self?.launchPicker()
@ -287,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 ?? ""
@ -346,19 +313,20 @@ open class DropdownSelect: EntryFieldBase {
UIAccessibility.post(notification: .layoutChanged, argument: fieldStackView) UIAccessibility.post(notification: .layoutChanged, argument: fieldStackView)
} }
open override func layoutSubviews() { open override var canBecomeFirstResponder: Bool {
super.layoutSubviews() return dropdownField.canBecomeFirstResponder
titleLabelWidthConstraint?.constant = containerView.frame.width
titleLabelWidthConstraint?.isActive = helperTextPlacement == .right
} }
open override var canBecomeFirstResponder: Bool { true } open override func becomeFirstResponder() -> Bool {
return dropdownField.becomeFirstResponder()
}
open override var canResignFirstResponder: Bool {
return dropdownField.canResignFirstResponder
}
open override func resignFirstResponder() -> Bool { open override func resignFirstResponder() -> Bool {
if dropdownField.isFirstResponder { return dropdownField.resignFirstResponder()
dropdownField.resignFirstResponder()
}
return super.resignFirstResponder()
} }
} }
@ -375,7 +343,7 @@ extension DropdownSelect: UIPickerViewDelegate, UIPickerViewDataSource {
dropdownField.resignFirstResponder() dropdownField.resignFirstResponder()
} }
optionsPicker.isHidden = !optionsPicker.isHidden optionsPicker.isHidden = !optionsPicker.isHidden
setNeedsUpdate() updateContainerView()
} }
public func numberOfComponents(in pickerView: UIPickerView) -> Int { public func numberOfComponents(in pickerView: UIPickerView) -> Int {
@ -396,6 +364,5 @@ extension DropdownSelect: UIPickerViewDelegate, UIPickerViewDataSource {
selectId = row selectId = row
updateSelectedOptionLabel(option: options[row]) updateSelectedOptionLabel(option: options[row])
sendActions(for: .valueChanged) sendActions(for: .valueChanged)
self.onItemSelected?(row, options[row])
} }
} }

View File

@ -56,10 +56,12 @@ open class Icon: View {
if let hex = color.hexString, !UIColor.isVDSColor(color: color) { if let hex = color.hexString, !UIColor.isVDSColor(color: color) {
print("icon.color is not a VDSColor. Hex: \(hex) is not a supported 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. /// Size of the icon.
open var size: Size = .medium { didSet { setNeedsUpdate() } } open var size: Size = .medium { didSet { setNeedsUpdate() } }
@ -98,14 +100,7 @@ open class Icon: View {
open override func updateView() { open override func updateView() {
super.updateView() super.updateView()
//get the color for the image //get the color for the image
var imageColor = color let imageColor = colorConfiguration.getColor(surface)
//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
}
//get the image name //get the image name
//set the image //set the image

View File

@ -40,34 +40,37 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
//-------------------------------------------------- //--------------------------------------------------
// 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
@ -87,7 +90,16 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
} }
}() }()
open var rules = [AnyRule<String>]() /// 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!
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Configuration Properties // MARK: - Configuration Properties
@ -200,32 +212,30 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
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 // Add mainStackView to the view
addSubview(primaryStackView) addSubview(mainStackView)
//create the wrapping view mainStackView.pinToSuperView()
heightConstraint = containerView.heightGreaterThanEqualTo(constant: containerSize.height)
widthConstraint = containerView.width(constant: frame.size.width)
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))
@ -239,27 +249,31 @@ 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)
mainStackView.addArrangedSubview(contentStackView)
primaryStackView // Initial position of the helper label
.pinTop() updateHelperTextPosition()
.pinLeading() }
.pinTrailing(0, .defaultHigh)
.pinBottom(0, .defaultHigh)
titleLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() /// Updates the UI
errorLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() open override func updateView() {
helperLabel.textColorConfiguration = secondaryColorConfiguration.eraseToAnyColorable() super.updateView()
updateContainerView()
updateTitleLabel()
updateErrorLabel()
updateHelperLabel()
} }
/// Resets to default settings. /// Resets to default settings.
@ -287,45 +301,9 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
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
//--------------------------------------------------
private 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.")
@ -336,19 +314,11 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
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() {
@ -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

@ -20,7 +20,7 @@ extension InputField {
case jcb case jcb
case unionPay case unionPay
func imageName(surface: Surface) -> String { public func imageName(surface: Surface) -> String {
func getImageName(_ surface: Surface, name: String) -> String { func getImageName(_ surface: Surface, name: String) -> String {
return surface == .light ? name : "\(name)-inverted" return surface == .light ? name : "\(name)-inverted"
} }
@ -98,10 +98,33 @@ extension InputField {
self.keyboardType = .numberPad self.keyboardType = .numberPad
} }
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) { override func updateView(_ inputField: InputField) {
minWidth = 288.0 minWidth = 288.0
leftImageName = inputField.cardType.imageName(surface: inputField.surface)
super.updateView(inputField) 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) { override func appendRules(_ inputField: InputField) {
@ -120,6 +143,8 @@ extension InputField {
value = nil value = nil
inputField.cardType = .generic inputField.cardType = .generic
textField.text = "" textField.text = ""
inputField.validate()
updateLeftImage(inputField)
} }
override func textFieldDidEndEditing(_ inputField: InputField, textField: UITextField) { override func textFieldDidEndEditing(_ inputField: InputField, textField: UITextField) {
@ -183,14 +208,12 @@ extension InputField {
} }
internal func updateCardTypeIcon(_ inputField: InputField, rawNumber: String) { internal func updateCardTypeIcon(_ inputField: InputField, rawNumber: String) {
defer { inputField.setNeedsUpdate() } if rawNumber.count >= 4 {
inputField.cardType = CreditCardType.from(cardNumber: rawNumber)
guard rawNumber.count >= 4 else { } else {
inputField.cardType = .generic inputField.cardType = .generic
return
} }
updateLeftImage(inputField)
inputField.cardType = CreditCardType.from(cardNumber: rawNumber)
} }
internal func maskCreditCardNumber(_ cardType: CreditCardType, number: String) -> String { internal func maskCreditCardNumber(_ cardType: CreditCardType, number: String) -> String {

View File

@ -39,7 +39,6 @@ extension InputField {
class FieldTypeHandler: NSObject { class FieldTypeHandler: NSObject {
var keyboardType: UIKeyboardType var keyboardType: UIKeyboardType
var minWidth: CGFloat = 40.0 var minWidth: CGFloat = 40.0
var leftImageName: String?
var actionModel: TextLinkModel? var actionModel: TextLinkModel?
var toolTipModel: Tooltip.TooltipModel? var toolTipModel: Tooltip.TooltipModel?
var isSecureTextEntry = false var isSecureTextEntry = false
@ -60,14 +59,9 @@ extension InputField {
//textField //textField
inputField.textField.isSecureTextEntry = isSecureTextEntry inputField.textField.isSecureTextEntry = isSecureTextEntry
//leftIcon
if let leftImageName {
inputField.leftImageView.image = BundleManager.shared.image(for: leftImageName)
}
inputField.leftImageView.isHidden = leftImageName == nil
//actionLink //actionLink
inputField.actionTextLink.surface = inputField.surface inputField.actionTextLink.surface = inputField.surface
inputField.actionTextLink.isEnabled = inputField.isEnabled
if let actionModel { if let actionModel {
inputField.actionTextLink.text = actionModel.text inputField.actionTextLink.text = actionModel.text
inputField.actionTextLink.onClick = { _ in inputField.actionTextLink.onClick = { _ in
@ -88,6 +82,8 @@ extension InputField {
if let toolTipModel { if let toolTipModel {
inputField.tooltipModel = toolTipModel inputField.tooltipModel = toolTipModel
} }
inputField.textField.leftView = nil
} }
func appendRules(_ inputField: InputField) {} func appendRules(_ inputField: InputField) {}

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
@ -99,15 +98,6 @@ open class InputField: EntryFieldBase {
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forDisabled: false) $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forDisabled: false)
}.eraseToAnyColorable() }.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 actionTextLink = TextLink().with { $0.contentEdgeInsets = .top(-2) }
open var actionTextLinkModel: TextLinkModel? { didSet { setNeedsUpdate() } } open var actionTextLinkModel: TextLinkModel? { didSet { setNeedsUpdate() } }
@ -173,24 +163,15 @@ 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) bottomContainerStackView.insertArrangedSubview(successLabel, at: 0)
primaryStackView.setCustomSpacing(8, after: successLabel)
fieldStackView.addArrangedSubview(actionTextLink) fieldStackView.addArrangedSubview(actionTextLink)
@ -203,15 +184,7 @@ open class InputField: EntryFieldBase {
} }
open override func getFieldContainer() -> UIView { open override func getFieldContainer() -> UIView {
// stackview for controls in EntryFieldBase.controlContainerView return textField
let stackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.axis = .horizontal
$0.spacing = VDSLayout.space3X
}
stackView.addArrangedSubview(leftImageView)
stackView.addArrangedSubview(textField)
return stackView
} }
/// Resets to default settings. /// Resets to default settings.
@ -262,27 +235,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,26 +270,27 @@ open class InputField: EntryFieldBase {
set { super.accessibilityElements = newValue } set { super.accessibilityElements = newValue }
} }
open override func layoutSubviews() { open override var canBecomeFirstResponder: Bool {
super.layoutSubviews() return textField.canBecomeFirstResponder
titleLabelWidthConstraint?.constant = containerView.frame.width
titleLabelWidthConstraint?.isActive = helperTextPlacement == .right
} }
open override var canBecomeFirstResponder: Bool { true } open override func becomeFirstResponder() -> Bool {
return textField.becomeFirstResponder()
}
open override var canResignFirstResponder: Bool {
return textField.canResignFirstResponder
}
open override func resignFirstResponder() -> Bool { open override func resignFirstResponder() -> Bool {
if textField.isFirstResponder { return textField.resignFirstResponder()
textField.resignFirstResponder()
}
return super.resignFirstResponder()
} }
} }
extension InputField: UITextFieldDelegate { extension InputField: UITextFieldDelegate {
public func textFieldDidBeginEditing(_ textField: UITextField) { public func textFieldDidBeginEditing(_ textField: UITextField) {
fieldType.handler().textFieldDidBeginEditing(self, textField: textField) fieldType.handler().textFieldDidBeginEditing(self, textField: textField)
setNeedsUpdate() updateContainerView()
} }
public func textFieldDidEndEditing(_ textField: UITextField) { public func textFieldDidEndEditing(_ textField: UITextField) {
@ -349,10 +302,9 @@ extension InputField: UITextFieldDelegate {
fieldType.handler().textFieldDidChangeSelection(self, textField: textField) fieldType.handler().textFieldDidChangeSelection(self, textField: textField)
if fieldType.handler().validateOnChange { if fieldType.handler().validateOnChange {
validate() validate()
} else {
setNeedsUpdate()
} }
sendActions(for: .valueChanged) sendActions(for: .valueChanged)
setNeedsUpdate()
} }
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

View File

@ -55,17 +55,17 @@ open class TextField: UITextField {
} }
open func initialSetup() { open func initialSetup() {
let doneToolbar: UIToolbar = UIToolbar() let accessView = UIView(frame: .init(origin: .zero, size: .init(width: UIScreen.main.bounds.width, height: 44)))
doneToolbar.translatesAutoresizingMaskIntoConstraints = false accessView.backgroundColor = .white
doneToolbar.barStyle = .default accessView.addBorder(side: .top, width: 1, color: .lightGray)
let done = UIButton(type: .system)
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) done.setTitle("Done", for: .normal)
let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self.doneButtonAction)) done.translatesAutoresizingMaskIntoConstraints = false
done.accessibilityHint = "Double tap to finish editing." done.addTarget(self, action: #selector(doneButtonAction), for: .touchUpInside)
doneToolbar.items = [flexSpace, done] accessView.addSubview(done)
doneToolbar.sizeToFit() done.pinCenterY()
.pinTrailing(16)
inputAccessoryView = doneToolbar inputAccessoryView = accessView
} }
@objc func doneButtonAction() { @objc func doneButtonAction() {

View File

@ -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 { open override func resignFirstResponder() -> Bool {
if textView.isFirstResponder { return textView.resignFirstResponder()
textView.resignFirstResponder()
}
return super.resignFirstResponder()
} }
//-------------------------------------------------- //--------------------------------------------------

View File

@ -104,19 +104,17 @@ open class TextView: UITextView, ViewProtocol {
open func setup() { open func setup() {
translatesAutoresizingMaskIntoConstraints = false let accessView = UIView(frame: .init(origin: .zero, size: .init(width: UIScreen.main.bounds.width, height: 44)))
let doneToolbar: UIToolbar = UIToolbar() accessView.backgroundColor = .white
doneToolbar.translatesAutoresizingMaskIntoConstraints = false accessView.addBorder(side: .top, width: 1, color: .lightGray)
doneToolbar.barStyle = .default let done = UIButton(type: .system)
done.setTitle("Done", for: .normal)
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) done.translatesAutoresizingMaskIntoConstraints = false
let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self.doneButtonAction)) done.addTarget(self, action: #selector(doneButtonAction), for: .touchUpInside)
done.accessibilityHint = "Double tap to finish editing." accessView.addSubview(done)
doneToolbar.items = [flexSpace, done] done.pinCenterY()
doneToolbar.sizeToFit() .pinTrailing(16)
inputAccessoryView = accessView
inputAccessoryView = doneToolbar
} }
@objc func doneButtonAction() { @objc func doneButtonAction() {