Merge branch 'refactor/entryFieldBase' into 'develop'
refactor of EntryFieldBase See merge request BPHV_MIPS/vds_ios!229
This commit is contained in:
commit
ac278816ae
@ -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
|
||||
//--------------------------------------------------
|
||||
@ -114,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
|
||||
@ -136,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
|
||||
@ -161,20 +149,21 @@ 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
|
||||
fieldStackView
|
||||
containerView
|
||||
.publisher(for: UITapGestureRecognizer())
|
||||
.sink { [weak self] _ in
|
||||
self?.launchPicker()
|
||||
@ -287,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 ?? ""
|
||||
@ -345,20 +312,21 @@ 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
|
||||
}
|
||||
|
||||
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 {
|
||||
if dropdownField.isFirstResponder {
|
||||
dropdownField.resignFirstResponder()
|
||||
}
|
||||
return super.resignFirstResponder()
|
||||
return dropdownField.resignFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
@ -375,7 +343,7 @@ extension DropdownSelect: UIPickerViewDelegate, UIPickerViewDataSource {
|
||||
dropdownField.resignFirstResponder()
|
||||
}
|
||||
optionsPicker.isHidden = !optionsPicker.isHidden
|
||||
setNeedsUpdate()
|
||||
updateContainerView()
|
||||
}
|
||||
|
||||
public func numberOfComponents(in pickerView: UIPickerView) -> Int {
|
||||
@ -396,6 +364,5 @@ extension DropdownSelect: UIPickerViewDelegate, UIPickerViewDataSource {
|
||||
selectId = row
|
||||
updateSelectedOptionLabel(option: options[row])
|
||||
sendActions(for: .valueChanged)
|
||||
self.onItemSelected?(row, options[row])
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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<String>]()
|
||||
|
||||
//--------------------------------------------------
|
||||
// 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<String>]()
|
||||
|
||||
//--------------------------------------------------
|
||||
// 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<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
|
||||
//--------------------------------------------------
|
||||
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<EntryFieldBase>(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) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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) {}
|
||||
|
||||
@ -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
|
||||
@ -98,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() } }
|
||||
@ -172,9 +162,6 @@ 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
|
||||
@ -182,15 +169,9 @@ 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()
|
||||
|
||||
titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||
titleLabel.setContentHuggingPriority(.required, for: .horizontal)
|
||||
titleLabelWidthConstraint = titleLabel.width(constant: 0)
|
||||
|
||||
textField.heightAnchor.constraint(equalToConstant: 20).isActive = true
|
||||
textField.delegate = self
|
||||
primaryStackView.addArrangedSubview(successLabel)
|
||||
primaryStackView.setCustomSpacing(8, after: successLabel)
|
||||
bottomContainerStackView.insertArrangedSubview(successLabel, at: 0)
|
||||
|
||||
fieldStackView.addArrangedSubview(actionTextLink)
|
||||
|
||||
@ -203,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.
|
||||
@ -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() {
|
||||
super.updateRules()
|
||||
@ -318,26 +270,27 @@ 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
|
||||
}
|
||||
|
||||
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 {
|
||||
if textField.isFirstResponder {
|
||||
textField.resignFirstResponder()
|
||||
}
|
||||
return super.resignFirstResponder()
|
||||
return textField.resignFirstResponder()
|
||||
}
|
||||
}
|
||||
|
||||
extension InputField: UITextFieldDelegate {
|
||||
public func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||
fieldType.handler().textFieldDidBeginEditing(self, textField: textField)
|
||||
setNeedsUpdate()
|
||||
updateContainerView()
|
||||
}
|
||||
|
||||
public func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
@ -349,10 +302,9 @@ extension InputField: UITextFieldDelegate {
|
||||
fieldType.handler().textFieldDidChangeSelection(self, textField: textField)
|
||||
if fieldType.handler().validateOnChange {
|
||||
validate()
|
||||
} else {
|
||||
setNeedsUpdate()
|
||||
}
|
||||
sendActions(for: .valueChanged)
|
||||
setNeedsUpdate()
|
||||
}
|
||||
|
||||
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -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() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user