Digital ACT-191 ONEAPP-9311 story: control width can be set to auto (default) or value (pixel) , updating stepper view when size changes.

This commit is contained in:
Vasavi Kanamarlapudi 2024-07-24 20:40:16 +05:30
parent 161f690488
commit 0210e0d128
4 changed files with 161 additions and 69 deletions

View File

@ -299,7 +299,7 @@ extension DropdownSelect: UIPickerViewDelegate, UIPickerViewDataSource {
dropdownField.resignFirstResponder() dropdownField.resignFirstResponder()
} }
optionsPicker.isHidden = !optionsPicker.isHidden optionsPicker.isHidden = !optionsPicker.isHidden
updateContainerView() updateContainerView(flag: true)
updateErrorLabel() updateErrorLabel()
} }

View File

@ -40,17 +40,17 @@ open class InputStepper: EntryFieldBase {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Public Properties // MARK: - Public Properties
//-------------------------------------------------- //--------------------------------------------------
open var controlWidth: String? { /// Accepts a string or number value to control the width of input stepper.
get { _controlWidth } /// auto(default) - The control's width is determined by the combined width of the value, trailing text and padding
set { /// Value - The control's width can be set to a fixed pixel or percentage value.
setControlWidth(newValue) open var controlWidth: String? = "auto" { didSet { setNeedsUpdate() } }
setNeedsUpdate()
}
}
/// Default value of the input stepper, defaults to '0'. /// Default value of the input stepper, defaults to '0'.
open var defaultValue:Int = 0 { didSet { setNeedsUpdate() } } open var defaultValue:Int = 0 { didSet { setNeedsUpdate() } }
/// Allows an id to be passed to input stepper.
open var id: Int? { didSet { setNeedsUpdate() } }
/// Maximum value of the input stepper, defaults to '99'. /// Maximum value of the input stepper, defaults to '99'.
open var maxValue: Int? { open var maxValue: Int? {
get { return _maxValue } get { return _maxValue }
@ -63,7 +63,7 @@ open class InputStepper: EntryFieldBase {
setNeedsUpdate() setNeedsUpdate()
} }
} }
/// Minimum value of the input stepper, defaults to '0'. /// Minimum value of the input stepper, defaults to '0'.
open var minValue: Int? { open var minValue: Int? {
get { return _minValue } get { return _minValue }
@ -78,11 +78,10 @@ open class InputStepper: EntryFieldBase {
} }
/// The size of the input stepper. Defaults to 'large'. /// The size of the input stepper. Defaults to 'large'.
open var size: Size { open var size: Size = .large {
get { return _size } didSet {
set { updateStepperContainerViewSize()
_size = newValue setNeedsUpdate()
updateSize()
} }
} }
@ -92,15 +91,23 @@ open class InputStepper: EntryFieldBase {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Private Properties // MARK: - Private Properties
//-------------------------------------------------- //--------------------------------------------------
internal var _controlWidth = "auto"
internal var _maxValue: Int = 99 internal var _maxValue: Int = 99
internal var _minValue: Int = 0 internal var _minValue: Int = 0
internal var _size: Size = .large
private var largeMinWidth = 121 /// This is the view that will be wrapped with the border for userInteraction.
private var smallMinWidth = 90 /// The only subview of this view is the stepperStackView.
internal var stepperContainerView = View().with {
$0.isAccessibilityElement = true
}
let decrementButton = ButtonIcon().with { internal var stepperStackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
$0.axis = .horizontal
$0.distribution = .fill
$0.alignment = .fill
}
internal var decrementButton = ButtonIcon().with {
$0.kind = .ghost $0.kind = .ghost
$0.iconName = Icon.Name(name: "minus") $0.iconName = Icon.Name(name: "minus")
$0.iconOffset = .init(x: -2, y: 0) $0.iconOffset = .init(x: -2, y: 0)
@ -109,7 +116,7 @@ open class InputStepper: EntryFieldBase {
$0.backgroundColor = .clear $0.backgroundColor = .clear
} }
let incrementButton = ButtonIcon().with { internal var incrementButton = ButtonIcon().with {
$0.kind = .ghost $0.kind = .ghost
$0.iconName = Icon.Name(name: "plus") $0.iconName = Icon.Name(name: "plus")
$0.iconOffset = .init(x: 2, y: 0) $0.iconOffset = .init(x: 2, y: 0)
@ -118,17 +125,31 @@ open class InputStepper: EntryFieldBase {
$0.backgroundColor = .clear $0.backgroundColor = .clear
} }
let textLabel = Label().with { internal var textLabel = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical) $0.setContentCompressionResistancePriority(.required, for: .vertical)
$0.textStyle = .boldBodyLarge $0.textStyle = .boldBodyLarge
$0.backgroundColor = .clear $0.numberOfLines = 1
$0.lineBreakMode = .byTruncatingTail
} }
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Configuration // MARK: - Constraints
//-------------------------------------------------- //--------------------------------------------------
private var labelColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight , VDSColor.elementsPrimaryOndark) internal var stepperWidthConstraint: NSLayoutConstraint?
private var labelDisabledColorConfiguration = SurfaceColorConfiguration(VDSColor.interactiveDisabledOnlight , VDSColor.interactiveDisabledOndark) internal var stepperHeightConstraint: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Configuration Properties
//--------------------------------------------------
internal override var containerSize: CGSize { CGSize(width: size == .large ? largeMinWidth : smallMinWidth, height: size == .large ? largeMinHeight : smallMinHeight) }
internal var largeMinWidth = 121
internal var smallMinWidth = 90
internal var largeMinHeight = 44
internal var smallMinHeight = 32
internal let labelColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight , VDSColor.elementsPrimaryOndark)
internal let labelDisabledColorConfiguration = SurfaceColorConfiguration(VDSColor.interactiveDisabledOnlight , VDSColor.interactiveDisabledOndark)
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Lifecycle // MARK: - Lifecycle
@ -137,38 +158,68 @@ open class InputStepper: EntryFieldBase {
super.initialSetup() super.initialSetup()
} }
/// 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()
// accessibility
isAccessibilityElement = false isAccessibilityElement = false
accessibilityLabel = "Input Stepper" accessibilityLabel = "Input Stepper"
// Set initial states
containerView.isEnabled = false containerView.isEnabled = false
statusIcon.isHidden = true
// Add listeners
decrementButton.onClick = { _ in self.decrementButtonClick() } decrementButton.onClick = { _ in self.decrementButtonClick() }
incrementButton.onClick = { _ in self.incrementButtonClick() } incrementButton.onClick = { _ in self.incrementButtonClick() }
// setting color config
textLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable()
} }
open override func getFieldContainer() -> UIView { open override func getFieldContainer() -> UIView {
// stackview for controls in EntryFieldBase.controlContainerView stepperStackView.addArrangedSubview(decrementButton)
let controlStackView = UIStackView().with { stepperStackView.addArrangedSubview(textLabel)
$0.translatesAutoresizingMaskIntoConstraints = false stepperStackView.addArrangedSubview(incrementButton)
$0.axis = .horizontal
$0.spacing = VDSLayout.space3X // Set space between decrement button, label, and increment button relative to input Stepper size.
$0.backgroundColor = .clear let space = size == .large ? VDSLayout.space3X : VDSLayout.space2X
} stepperStackView.setCustomSpacing(space, after: decrementButton)
controlStackView.addArrangedSubview(decrementButton) stepperStackView.setCustomSpacing(space, after: textLabel)
controlStackView.addArrangedSubview(textLabel)
controlStackView.addArrangedSubview(incrementButton) // Update Edge insets relative to input Stepper size.
return controlStackView stepperStackView.pinToSuperView(.uniform(size == .large ? 6.0 : VDSLayout.space1X))
// stepperContainerView for controls in EntryFieldBase.controlContainerView
stepperContainerView.addSubview(stepperStackView)
stepperWidthConstraint = stepperContainerView.widthAnchor.constraint(equalToConstant: containerSize.width)
stepperWidthConstraint?.deactivate()
stepperHeightConstraint = stepperContainerView.heightAnchor.constraint(equalToConstant: containerSize.height)
stepperHeightConstraint?.deactivate()
return stepperContainerView
} }
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() { open override func updateView() {
super.updateView() super.updateView()
updateContainerView(flag: false)
textLabel.text = String(defaultValue) + " " + (trailingText ?? "")
decrementButton.surface = surface
incrementButton.surface = surface
textLabel.surface = surface
statusIcon.isHidden = true statusIcon.isHidden = true
updateConstraintsToFieldStackView(flag: false)
// Update label text, style, color, ande surface.
textLabel.text = String(defaultValue) + " " + (trailingText ?? "")
textLabel.textStyle = size == .large ? .boldBodyLarge : .boldBodySmall
textLabel.textColorConfiguration = !isEnabled ? labelDisabledColorConfiguration.eraseToAnyColorable() : labelColorConfiguration.eraseToAnyColorable()
textLabel.surface = surface
// Update increment and decrement button.
updateButtonStates() updateButtonStates()
// Update stepper container border and corner radius.
setControlWidth(controlWidth)
updateContainerView(flag: false)
} }
/// Resets to default settings. /// Resets to default settings.
@ -204,7 +255,11 @@ open class InputStepper: EntryFieldBase {
} }
internal func updateButtonStates() { internal func updateButtonStates() {
textLabel.textColorConfiguration = !isEnabled ? labelDisabledColorConfiguration.eraseToAnyColorable() : labelColorConfiguration.eraseToAnyColorable() decrementButton.customContainerSize = size == .large ? 32 : 24
incrementButton.customContainerSize = size == .large ? 32 : 24
decrementButton.surface = surface
incrementButton.surface = surface
if isReadOnly || !isEnabled { if isReadOnly || !isEnabled {
decrementButton.isEnabled = false decrementButton.isEnabled = false
incrementButton.isEnabled = false incrementButton.isEnabled = false
@ -214,27 +269,42 @@ open class InputStepper: EntryFieldBase {
} }
} }
internal func updateSize() { // Update edge insets and height when size changes.
let value = size == .large ? 6.0 : VDSLayout.space1X internal func updateStepperContainerViewSize() {
updateConstraintsToFieldStackView(value: value) updateButtonStates()
// textLabel.textStyle = size == .large ? .boldBodyLarge : .boldBodySmall // Update Edge insets if size changes applied.
// textLabel.heightAnchor.constraint(equalToConstant: size == .large ? 44 : 32).activate() stepperStackView.removeFromSuperview()
stepperContainerView.addSubview(stepperStackView)
// decrementButton.customContainerSize = size == .large ? 32 : 24 stepperStackView.pinToSuperView(.uniform(size == .large ? 6.0 : VDSLayout.space1X))
// incrementButton.customContainerSize = size == .large ? 32 : 24
// Update height if size changes applied.
stepperHeightConstraint?.deactivate()
stepperHeightConstraint = stepperContainerView.heightAnchor.constraint(equalToConstant: containerSize.height)
stepperHeightConstraint?.activate()
} }
// Set control width to input stepper.
internal func setControlWidth(_ text: String?) { internal func setControlWidth(_ text: String?) {
if let text, text == "auto" { if let text, text == "auto" {
// Set fixed width relative to default value, trailing text label stepperWidthConstraint?.deactivate()
} else if let controlWidth = Int(text ?? "") { } else if let controlWidth = Int(text ?? "") {
// Use provided new width either pixel or percentage // Set controlWidth provided which is either pixel or percentage
width = CGFloat(controlWidth) let width = width ?? CGFloat(containerView.frame.size.width)
} else { updateStepperContainerWidth(controlWidth: CGFloat(controlWidth), width: width)
// Use EntryFieldBase width }
}
} }
// Handling the controlwidth without going beyond the width of the parent container.
internal func updateStepperContainerWidth(controlWidth: CGFloat, width: CGFloat) {
if controlWidth >= containerSize.width && controlWidth <= width {
stepperWidthConstraint?.deactivate()
stepperWidthConstraint?.constant = controlWidth
stepperWidthConstraint?.activate()
} else if controlWidth >= width {
stepperWidthConstraint?.deactivate()
stepperWidthConstraint?.constant = width
stepperWidthConstraint?.activate()
}
}
} }

View File

@ -227,7 +227,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
get { fatalError("must be read from subclass")} get { fatalError("must be read from subclass")}
} }
open var defaultValue: AnyHashable? { didSet { setNeedsUpdate() } } // open var defaultValue: AnyHashable? { didSet { setNeedsUpdate() } }
open var isRequired: Bool = false { didSet { setNeedsUpdate() } } open var isRequired: Bool = false { didSet { setNeedsUpdate() } }
@ -346,7 +346,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
/// Updates the UI /// Updates the UI
open override func updateView() { open override func updateView() {
super.updateView() super.updateView()
updateContainerView() updateContainerView(flag: true)
updateContainerWidth() updateContainerWidth()
updateTitleLabel() updateTitleLabel()
updateErrorLabel() updateErrorLabel()
@ -372,7 +372,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
transparentBackground = false transparentBackground = false
width = nil width = nil
inputId = nil inputId = nil
defaultValue = nil // defaultValue = nil
isRequired = false isRequired = false
isReadOnly = false isReadOnly = false
onChange = nil onChange = nil
@ -401,7 +401,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
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.")
} }
/// Container for the area in which helper or error text presents. /// Container for the area in which helper or error text presents.
open func getBottomContainer() -> UIView { open func getBottomContainer() -> UIView {
return bottomContainerStackView return bottomContainerStackView
@ -520,13 +520,35 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
} }
} }
internal func updateContainerView() { internal func updateContainerView(flag: Bool) {
containerView.backgroundColor = containerBackgroundColor if flag {
containerView.layer.borderColor = borderColorConfiguration.getColor(self).cgColor containerView.backgroundColor = containerBackgroundColor
containerView.layer.borderWidth = VDSFormControls.borderWidth containerView.layer.borderColor = borderColorConfiguration.getColor(self).cgColor
containerView.layer.cornerRadius = VDSFormControls.borderRadius containerView.layer.borderWidth = VDSFormControls.borderWidth
containerView.layer.cornerRadius = VDSFormControls.borderRadius
} else {
containerView.backgroundColor = .clear
containerView.layer.borderColor = nil
containerView.layer.borderWidth = 0
containerView.layer.cornerRadius = 0
fieldStackView.backgroundColor = containerBackgroundColor
fieldStackView.layer.borderColor = borderColorConfiguration.getColor(self).cgColor
fieldStackView.layer.borderWidth = VDSFormControls.borderWidth
fieldStackView.layer.cornerRadius = containerView.frame.size.height / 2
}
} }
/// Update constraints to containerStackView which has horizontal stack in which user interacts.
internal func updateConstraintsToFieldStackView(flag: Bool) {
fieldStackView.removeFromSuperview()
containerView.addSubview(fieldStackView)
if flag {
fieldStackView.pinToSuperView(.uniform(VDSLayout.space3X))
} else {
fieldStackView.pinTop().pinLeading().pinBottom().pinTrailingLessThanOrEqualTo()
}
}
internal func updateContainerWidth() { internal func updateContainerWidth() {
widthConstraint?.deactivate() widthConstraint?.deactivate()
trailingLessThanEqualsConstraint?.deactivate() trailingLessThanEqualsConstraint?.deactivate()

View File

@ -287,7 +287,7 @@ open class InputField: EntryFieldBase {
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)
updateContainerView() updateContainerView(flag: true)
updateErrorLabel() updateErrorLabel()
} }