// // InputStepper.swift // VDS // // Created by Kanamarlapudi, Vasavi on 24/06/24. // import Foundation import UIKit import VDSCoreTokens import Combine /// A stepper is a two-segment control that people use to increase or decrease an incremental value. @objc(VDSInputStepper) open class InputStepper: EntryFieldBase { //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- required public init() { super.init(frame: .zero) } public override init(frame: CGRect) { super.init(frame: .zero) } public required init?(coder: NSCoder) { super.init(coder: coder) } //-------------------------------------------------- // MARK: - Enums //-------------------------------------------------- /// Enum used to describe the size of Input Stepper. public enum Size: String, CaseIterable { case large, small } //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- open var controlWidth: String? { get { _controlWidth } set { setControlWidth(newValue) setNeedsUpdate() } } /// Default value of the input stepper, defaults to '0'. open var defaultValue:Int = 0 { didSet { setNeedsUpdate() } } /// Maximum value of the input stepper, defaults to '99'. open var maxValue: Int? { get { return _maxValue } set { if let newValue, newValue <= 99 && newValue > 0 { _maxValue = newValue } else { _maxValue = 99 } setNeedsUpdate() } } /// Minimum value of the input stepper, defaults to '0'. open var minValue: Int? { get { return _minValue } set { if let newValue, newValue >= 0 && newValue < 99 { _minValue = newValue } else { _minValue = 0 } setNeedsUpdate() } } /// The size of the input stepper. Defaults to 'large'. open var size: Size { get { return _size } set { _size = newValue updateSize() } } /// Accepts any text or character to appear next to input stepper value. open var trailingText: String? { didSet { setNeedsUpdate() } } //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- internal var _controlWidth = "auto" internal var _maxValue: Int = 99 internal var _minValue: Int = 0 internal var _size: Size = .large private var largeMinWidth = 121 private var smallMinWidth = 90 let decrementButton = ButtonIcon().with { $0.kind = .ghost $0.iconName = Icon.Name(name: "minus") $0.iconOffset = .init(x: -2, y: 0) $0.customContainerSize = 32 $0.icon.customSize = 16 $0.backgroundColor = .clear } let incrementButton = ButtonIcon().with { $0.kind = .ghost $0.iconName = Icon.Name(name: "plus") $0.iconOffset = .init(x: 2, y: 0) $0.customContainerSize = 32 $0.icon.customSize = 16 $0.backgroundColor = .clear } let textLabel = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) $0.textStyle = .boldBodyLarge $0.backgroundColor = .clear } //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- open override func initialSetup() { super.initialSetup() } open override func setup() { super.setup() isAccessibilityElement = false accessibilityLabel = "Input Stepper" containerView.isEnabled = false } open override func getFieldContainer() -> UIView { // stackview for controls in EntryFieldBase.controlContainerView let controlStackView = UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .horizontal $0.spacing = VDSLayout.space3X $0.backgroundColor = .clear } controlStackView.addArrangedSubview(decrementButton) controlStackView.addArrangedSubview(textLabel) controlStackView.addArrangedSubview(incrementButton) return controlStackView } open override func updateView() { super.updateView() updateContainerView(flag: false) textLabel.text = String(defaultValue) + " " + (trailingText ?? "") decrementButton.surface = surface incrementButton.surface = surface textLabel.surface = surface statusIcon.isHidden = true } /// Resets to default settings. open override func reset() { super.reset() textLabel.reset() textLabel.textStyle = .boldBodyLarge textLabel.text = "" controlWidth = nil minValue = nil maxValue = nil trailingText = nil helperTextPlacement = .bottom } //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- internal func updateSize() { let value = size == .large ? 6.0 : VDSLayout.space1X updateConstraintsToFieldStackView(value: value) // textLabel.textStyle = size == .large ? .boldBodyLarge : .boldBodySmall // textLabel.heightAnchor.constraint(equalToConstant: size == .large ? 44 : 32).activate() // decrementButton.customContainerSize = size == .large ? 32 : 24 // incrementButton.customContainerSize = size == .large ? 32 : 24 } internal func setControlWidth(_ text: String?) { if let text, text == "auto" { // Set fixed width relative to default value, trailing text label } else if let controlWidth = Int(text ?? "") { // Use provided new width either pixel or percentage width = CGFloat(controlWidth) } else { // Use EntryFieldBase width } } }