Merge branch 'develop' of https://gitlab.verizon.com/BPHV_MIPS/vds_ios into vasavk/priceLockup

This commit is contained in:
Vasavi Kanamarlapudi 2024-08-15 16:47:16 +05:30
commit 1bd58256f7
51 changed files with 1161 additions and 576 deletions

View File

@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
180636C72C29B0A400C92D86 /* InputStepper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180636C62C29B0A400C92D86 /* InputStepper.swift */; };
180636C92C29B0DF00C92D86 /* InputStepperLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 180636C82C29B0DF00C92D86 /* InputStepperLog.txt */; };
1808BEBC2BA41C3200129230 /* CarouselScrollbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */; };
1832AC572BA0791D008AE476 /* BreadcrumbCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */; };
184023452C61E7AD00A412C8 /* PriceLockup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184023442C61E7AD00A412C8 /* PriceLockup.swift */; };
@ -209,6 +211,8 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
180636C62C29B0A400C92D86 /* InputStepper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputStepper.swift; sourceTree = "<group>"; };
180636C82C29B0DF00C92D86 /* InputStepperLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = InputStepperLog.txt; sourceTree = "<group>"; };
1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselScrollbar.swift; sourceTree = "<group>"; };
1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CarouselScrollbarChangeLog.txt; sourceTree = "<group>"; };
1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbCellItem.swift; sourceTree = "<group>"; };
@ -453,6 +457,15 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
180636C52C29B06200C92D86 /* InputStepper */ = {
isa = PBXGroup;
children = (
180636C62C29B0A400C92D86 /* InputStepper.swift */,
180636C82C29B0DF00C92D86 /* InputStepperLog.txt */,
);
path = InputStepper;
sourceTree = "<group>";
};
1808BEBA2BA41B1D00129230 /* CarouselScrollbar */ = {
isa = PBXGroup;
children = (
@ -694,6 +707,7 @@
EAC58C1F2BF127F000BA39FA /* DatePicker */,
186D13C92BBA8A3500986B53 /* DropdownSelect */,
EA985BF3296C609E00F2FF2E /* Icon */,
180636C52C29B06200C92D86 /* InputStepper */,
EA3362412892EF700071C351 /* Label */,
44604AD529CE195300E62B51 /* Line */,
EAD0688C2A55F801002E3A2D /* Loader */,
@ -1203,6 +1217,7 @@
EA3362072891E14D0071C351 /* VerizonNHGeDS-Regular.otf in Resources */,
EA3362062891E14D0071C351 /* VerizonNHGeTX-Regular.otf in Resources */,
EA3362052891E14D0071C351 /* VerizonNHGeDS-Bold.otf in Resources */,
180636C92C29B0DF00C92D86 /* InputStepperLog.txt in Resources */,
EAA5EEB928ECD24B003B3210 /* Icons.xcassets in Resources */,
EAA5EEE428F5B855003B3210 /* VerizonNHGDS-Light.otf in Resources */,
);
@ -1246,6 +1261,7 @@
files = (
445BA07829C07B3D0036A7C5 /* Notification.swift in Sources */,
EA5E304C294CBDD00082B959 /* TileContainer.swift in Sources */,
180636C72C29B0A400C92D86 /* InputStepper.swift in Sources */,
EAF7F0A6289B0CE000B287F5 /* Resetable.swift in Sources */,
EA985C2D296F03FE00F2FF2E /* TileletIconModels.swift in Sources */,
EA89200428AECF4B006B9984 /* UITextField+Publisher.swift in Sources */,

View File

@ -78,20 +78,30 @@ open class Control: UIControl, ViewProtocol, UserInfoable, Clickable {
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open func initialSetup() {
private func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
shouldUpdateView = false
setup()
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
}
}
open func setup() {
backgroundColor = .clear
translatesAutoresizingMaskIntoConstraints = false
insetsLayoutMarginsFromSafeArea = false
}
open func setDefaults() {
backgroundColor = .clear
surface = .light
isEnabled = true
onClick = nil
userInfo.removeAll()
}
open func updateView() { }
open func updateAccessibility() {
@ -108,13 +118,12 @@ open class Control: UIControl, ViewProtocol, UserInfoable, Clickable {
}
}
open func reset() {
backgroundColor = .clear
surface = .light
isEnabled = true
onClick = nil
userInfo.removeAll()
shouldUpdateView = false
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
}
//--------------------------------------------------

View File

@ -100,13 +100,21 @@ open class SelectorBase: Control, SelectorControlable {
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
/// Executed on initialization for this View.
open override func 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() {
super.setup()
isAccessibilityElement = true
accessibilityTraits = .button
}
open override func setDefaults() {
super.setDefaults()
onClick = { control in
control.toggle()
}
bridge_accessibilityLabelBlock = { [weak self] in
guard let self else { return "" }
return "\(Self.self)\(showError ? ", error" : "")"
@ -118,14 +126,6 @@ open class SelectorBase: Control, SelectorControlable {
}
}
/// 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 = true
accessibilityTraits = .button
}
open override func updateView() {
super.updateView()
setNeedsLayout()

View File

@ -65,25 +65,30 @@ open class SelectorGroupBase<SelectorItemType: Groupable>: Control, SelectorGrou
}
didSet {
setItemsActions()
for selector in items {
selector.onClick = { [weak self] handler in
self?.didSelect(handler)
self?.setNeedsUpdate()
}
selector.accessibilityAction = { [weak self] handler in
guard let handler = handler as? SelectorItemType else { return }
self?.didSelect(handler)
self?.setNeedsUpdate()
}
mainStackView.addArrangedSubview(selector)
}
}
}
open var onChangeSubscriber: AnyCancellable?
private func setItemsActions() {
for selector in items {
selector.onClick = { [weak self] handler in
self?.didSelect(handler)
self?.setNeedsUpdate()
}
selector.accessibilityAction = { [weak self] handler in
guard let handler = handler as? SelectorItemType else { return }
self?.didSelect(handler)
self?.setNeedsUpdate()
}
}
}
/// Whether the Control is enabled or not.
override open var isEnabled: Bool {
didSet {
@ -115,6 +120,11 @@ open class SelectorGroupBase<SelectorItemType: Groupable>: Control, SelectorGrou
.pinBottom(0, .defaultHigh)
}
open override func setDefaults() {
super.setDefaults()
onChange = nil
}
/// Handler for the Group to override on a select event.
/// - Parameter selectedControl: Selected Control the user interacted.
open func didSelect(_ selectedControl: SelectorItemType) {
@ -131,8 +141,8 @@ open class SelectorGroupBase<SelectorItemType: Groupable>: Control, SelectorGrou
/// Resets to default settings.
open override func reset() {
super.reset()
onChange = nil
items.forEach{ $0.reset() }
setItemsActions()
}
}

View File

@ -66,18 +66,21 @@ open class SelectorItemBase<Selector: SelectorBase>: Control, Errorable, Changea
/// Label used to render labelText.
open var label = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
$0.setContentCompressionResistancePriority(.required, for: .horizontal)
$0.textStyle = .boldBodyLarge
}
/// Label used to render childText.
open var childLabel = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
$0.setContentCompressionResistancePriority(.required, for: .horizontal)
$0.textStyle = .bodyLarge
}
/// Label used to render errorText.
open var errorLabel = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
$0.setContentCompressionResistancePriority(.required, for: .horizontal)
$0.textStyle = .bodyMedium
}
@ -157,9 +160,32 @@ open class SelectorItemBase<Selector: SelectorBase>: Control, Errorable, Changea
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
/// Executed on initialization for this View.
open override func 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() {
super.setup()
selectorView.isAccessibilityElement = true
isAccessibilityElement = false
addSubview(mainStackView)
mainStackView.isUserInteractionEnabled = false
mainStackView.addArrangedSubview(selectorStackView)
mainStackView.addArrangedSubview(errorLabel)
selectorStackView.addArrangedSubview(selectorView)
selectorStackView.addArrangedSubview(selectorLabelStackView)
selectorLabelStackView.addArrangedSubview(label)
selectorLabelStackView.addArrangedSubview(childLabel)
mainStackView
.pinTop()
.pinLeading()
.pinTrailing()
.pinBottom(0, .defaultHigh)
}
open override func setDefaults() {
super.setDefaults()
onClick = { [weak self] control in
guard let self, isEnabled else { return }
toggle()
@ -203,29 +229,23 @@ open class SelectorItemBase<Selector: SelectorBase>: Control, Errorable, Changea
guard let self else { return "" }
return !isEnabled ? "" : "Double tap to activate."
}
}
/// 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()
label.textStyle = .boldBodyLarge
childLabel.textStyle = .bodyLarge
errorLabel.textStyle = .bodyMedium
selectorView.isAccessibilityElement = true
isAccessibilityElement = false
addSubview(mainStackView)
labelText = nil
labelTextAttributes = nil
labelAttributedText = nil
childText = nil
childTextAttributes = nil
childAttributedText = nil
showError = false
errorText = nil
inputId = nil
isSelected = false
mainStackView.isUserInteractionEnabled = false
mainStackView.addArrangedSubview(selectorStackView)
mainStackView.addArrangedSubview(errorLabel)
selectorStackView.addArrangedSubview(selectorView)
selectorStackView.addArrangedSubview(selectorLabelStackView)
selectorLabelStackView.addArrangedSubview(label)
selectorLabelStackView.addArrangedSubview(childLabel)
mainStackView
.pinTop()
.pinLeading()
.pinTrailing()
.pinBottom(0, .defaultHigh)
onChange = nil
}
/// Used to make changes to the View based off a change events or from local properties.
@ -281,30 +301,10 @@ open class SelectorItemBase<Selector: SelectorBase>: Control, Errorable, Changea
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
label.reset()
childLabel.reset()
errorLabel.reset()
label.textStyle = .boldBodyLarge
childLabel.textStyle = .bodyLarge
errorLabel.textStyle = .bodyMedium
labelText = nil
labelTextAttributes = nil
labelAttributedText = nil
childText = nil
childTextAttributes = nil
childAttributedText = nil
showError = false
errorText = nil
inputId = nil
isSelected = false
onChange = nil
shouldUpdateView = true
setNeedsUpdate()
super.reset()
}
//--------------------------------------------------

View File

@ -12,7 +12,7 @@ import Combine
/// Base Class used to build Views.
@objcMembers
@objc(VDSView)
open class View: UIView, ViewProtocol, UserInfoable {
open class View: UIView, ViewProtocol, UserInfoable, Clickable {
//--------------------------------------------------
// MARK: - Initializers
@ -37,6 +37,7 @@ open class View: UIView, ViewProtocol, UserInfoable {
//--------------------------------------------------
open var subscribers = Set<AnyCancellable>()
open var onClickSubscriber: AnyCancellable?
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
@ -57,20 +58,30 @@ open class View: UIView, ViewProtocol, UserInfoable {
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open func initialSetup() {
private func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
shouldUpdateView = false
setup()
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
}
}
open func setup() {
backgroundColor = .clear
translatesAutoresizingMaskIntoConstraints = false
insetsLayoutMarginsFromSafeArea = false
}
open func setDefaults() {
backgroundColor = .clear
surface = .light
isEnabled = true
onClick = nil
userInfo.removeAll()
}
open func updateView() { }
open func updateAccessibility() {
@ -82,9 +93,10 @@ open class View: UIView, ViewProtocol, UserInfoable {
}
open func reset() {
backgroundColor = .clear
surface = .light
isEnabled = true
shouldUpdateView = false
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
}
open override func layoutSubviews() {

View File

@ -149,25 +149,28 @@ open class Badge: View {
maxWidthConstraint = label.widthLessThanEqualTo(constant: 0).with { $0.isActive = false }
clipsToBounds = true
}
open override func setDefaults() {
super.setDefaults()
bridge_accessibilityLabelBlock = { [weak self] in
guard let self else { return "" }
return text
}
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
label.reset()
label.lineBreakMode = .byTruncatingTail
label.textStyle = .boldBodySmall
fillColor = .red
text = ""
maxWidth = nil
numberOfLines = 1
shouldUpdateView = true
setNeedsUpdate()
}
/// Resets to default settings.
open override func reset() {
label.reset()
super.reset()
}
/// Used to make changes to the View based off a change events or from local properties.

View File

@ -305,17 +305,31 @@ open class BadgeIndicator: View {
}
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
label.reset()
open override func setDefaults() {
super.setDefaults()
label.lineBreakMode = .byTruncatingTail
label.textAlignment = .center
fillColor = .red
number = nil
shouldUpdateView = true
setNeedsUpdate()
kind = .simple
leadingCharacter = nil
trailingText = nil
size = .xxlarge
dotSize = nil
verticalPadding = nil
horizontalPadding = nil
hideDot = false
hideBorder = false
width = nil
height = nil
accessibilityText = nil
maximumDigits = .two
}
/// Resets to default settings.
open override func reset() {
label.reset()
super.reset()
}
/// Used to make changes to the View based off a change events or from local properties.

View File

@ -72,17 +72,15 @@ open class BreadcrumbItem: ButtonBase {
//--------------------------------------------------
// 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()
open override func setDefaults() {
super.setDefaults()
isAccessibilityElement = true
accessibilityTraits = .link
titleLabel?.numberOfLines = 0
titleLabel?.lineBreakMode = .byWordWrapping
contentHorizontalAlignment = .left
isAccessibilityElement = true
accessibilityTraits = .link
bridge_accessibilityHintBlock = { [weak self] in
guard let self else { return "" }
return !isEnabled ? "" : "Double tap to open."
@ -131,17 +129,4 @@ open class BreadcrumbItem: ButtonBase {
}
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
text = nil
accessibilityCustomActions = []
isAccessibilityElement = true
accessibilityTraits = .button
shouldUpdateView = true
setNeedsUpdate()
}
}

View File

@ -109,21 +109,18 @@ open class Breadcrumbs: View {
// MARK: - Overrides
//--------------------------------------------------
/// Executed on initialization for this View.
open override func initialSetup() {
super.initialSetup()
open override func setup() {
super.setup()
containerView.addSubview(collectionView)
collectionView.pinToSuperView()
addSubview(containerView)
containerView.pinToSuperView()
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
breadcrumbs.forEach { $0.reset() }
shouldUpdateView = true
setNeedsUpdate()
}
/// Used to make changes to the View based off a change events or from local properties.

View File

@ -223,16 +223,12 @@ open class Button: ButtonBase, Useable {
isAccessibilityElement = true
accessibilityTraits = .button
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
open override func setDefaults() {
super.setDefaults()
use = .primary
width = nil
size = .large
shouldUpdateView = true
setNeedsUpdate()
}
/// Used to make changes to the View based off a change events or from local properties.

View File

@ -97,13 +97,13 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable {
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open func initialSetup() {
private func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
backgroundColor = .clear
translatesAutoresizingMaskIntoConstraints = false
accessibilityCustomActions = []
shouldUpdateView = false
setup()
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
}
}
@ -111,10 +111,19 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable {
open func setup() {
translatesAutoresizingMaskIntoConstraints = false
}
open func setDefaults() {
backgroundColor = .clear
accessibilityCustomActions = []
titleLabel?.adjustsFontSizeToFitWidth = false
titleLabel?.lineBreakMode = .byTruncatingTail
titleLabel?.numberOfLines = 1
surface = .light
isEnabled = true
text = nil
onClick = nil
userInfo.removeAll()
}
open func updateView() {
@ -131,12 +140,7 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable {
open func reset() {
shouldUpdateView = false
surface = .light
isEnabled = true
text = nil
accessibilityCustomActions = []
onClick = nil
userInfo.removeAll()
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
}

View File

@ -167,15 +167,17 @@ open class ButtonGroup: View {
collectionView.reloadData()
}
open override func reset() {
super.reset()
shouldUpdateView = false
open override func setDefaults() {
super.setDefaults()
rowQuantityPhone = 0
rowQuantityTablet = 0
alignment = .center
childWidth = nil
}
open override func reset() {
buttons.forEach { $0.reset() }
shouldUpdateView = true
setNeedsUpdate()
super.reset()
}
open override func layoutSubviews() {

View File

@ -91,12 +91,7 @@ open class TextLink: ButtonBase {
open override func setup() {
super.setup()
isAccessibilityElement = true
accessibilityTraits = .link
//left align titleLabel in case this is pinned leading/trailing
//default is always set to center
contentHorizontalAlignment = .left
if let titleLabel {
addSubview(line)
line.pinLeading(titleLabel.leadingAnchor)
@ -106,12 +101,21 @@ open class TextLink: ButtonBase {
lineHeightConstraint = line.height(constant: 1)
lineHeightConstraint?.isActive = true
}
}
open override func setDefaults() {
super.setDefaults()
size = .large
accessibilityTraits = .link
//left align titleLabel in case this is pinned leading/trailing
//default is always set to center
contentHorizontalAlignment = .left
bridge_accessibilityHintBlock = { [weak self] in
guard let self else { return "" }
return !isEnabled ? "" : "Double tap to open."
}
}
/// Used to make changes to the View based off a change events or from local properties.
@ -123,18 +127,5 @@ open class TextLink: ButtonBase {
//always call last so the label is rendered
super.updateView()
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
text = nil
size = .large
accessibilityCustomActions = []
isAccessibilityElement = true
accessibilityTraits = .link
shouldUpdateView = true
setNeedsUpdate()
}
}

View File

@ -76,10 +76,8 @@ open class TextLinkCaret: ButtonBase {
//--------------------------------------------------
// 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()
open override func setDefaults() {
super.setDefaults()
//left align titleLabel in case this is pinned leading/trailing
//default is always set to center
contentHorizontalAlignment = .left
@ -88,11 +86,12 @@ open class TextLinkCaret: ButtonBase {
titleLabel?.numberOfLines = 0
titleLabel?.lineBreakMode = .byWordWrapping
iconPosition = .right
bridge_accessibilityHintBlock = { [weak self] in
guard let self else { return "" }
return !isEnabled ? "" : "Double tap to open."
}
}
/// Used to make changes to the View based off a change events or from local properties.
@ -100,14 +99,7 @@ open class TextLinkCaret: ButtonBase {
imageAttribute = CaretLabelAttribute(tintColor: textColor, position: iconPosition)
super.updateView()
}
/// Resets to default settings.
open override func reset() {
super.reset()
iconPosition = .right
text = nil
}
/// The natural size for the receiving view, considering only properties of the view itself.
open override var intrinsicContentSize: CGSize {
guard let titleLabel else { return super.intrinsicContentSize }

View File

@ -125,10 +125,6 @@ open class CalendarBase: Control, Changeable {
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
open override func 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() {
super.setup()
@ -154,6 +150,19 @@ open class CalendarBase: Control, Changeable {
collectionView.pinCenterX(anchor: containerView.centerXAnchor)
}
open override func setDefaults() {
super.setDefaults()
hideContainerBorder = false
hideCurrentDateIndicator = false
transparentBackground = false
activeDates = []
inactiveDates = []
indicators = []
minDate = Date()
maxDate = Date()
selectedDate = Date()
}
open override func updateView() {
super.updateView()
@ -174,17 +183,6 @@ open class CalendarBase: Control, Changeable {
containerView.layer.borderWidth = VDSFormControls.borderWidth
}
}
/// Resets to default settings.
open override func reset() {
super.reset()
hideContainerBorder = false
hideCurrentDateIndicator = false
transparentBackground = false
activeDates = []
inactiveDates = []
indicators = []
}
//--------------------------------------------------
// MARK: - Private Methods

View File

@ -196,11 +196,6 @@ open class Carousel: View {
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
/// Executed on initialization for this View.
open override func 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() {
super.setup()
@ -249,6 +244,18 @@ open class Carousel: View {
updatePaginationInset()
}
open override func setDefaults() {
super.setDefaults()
gutter = UIDevice.isIPad ? .gutter6X : .gutter3X
layout = UIDevice.isIPad ? .threeUP : .oneUP
onChange = nil
pagination = .init(kind: .lowContrast, floating: true)
paginationDisplay = .none
paginationInset = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X
peek = .standard
groupIndex = 0
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()
@ -279,19 +286,7 @@ open class Carousel: View {
updatePaginationControls()
addCarouselSlots()
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
layout = UIDevice.isIPad ? .threeUP : .oneUP
pagination = .init(kind: .lowContrast, floating: true)
paginationDisplay = .none
paginationInset = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X
gutter = UIDevice.isIPad ? .gutter6X : .gutter3X
peek = .standard
}
//--------------------------------------------------
// MARK: - Private Methods
//--------------------------------------------------

View File

@ -235,10 +235,6 @@ open class CarouselScrollbar: View {
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open override func initialSetup() {
super.initialSetup()
}
open override func setup() {
super.setup()
isAccessibilityElement = false

View File

@ -6,7 +6,7 @@ import Combine
/// A dropdown select is an expandable menu of predefined options that allows a customer to make a single selection.
@objcMembers
@objc(VDSDatePicker)
open class DatePicker: EntryFieldBase {
open class DatePicker: EntryFieldBase<String> {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@ -154,15 +154,12 @@ open class DatePicker: EntryFieldBase {
selectedDateLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable()
// tap gesture
containerView
.publisher(for: UITapGestureRecognizer())
.sink { [weak self] _ in
guard let self else { return }
if isEnabled && !isReadOnly {
showPopover()
}
containerView.onClick = { [weak self] _ in
guard let self else { return }
if isEnabled && !isReadOnly {
showPopover()
}
.store(in: &subscribers)
}
NotificationCenter.default
.publisher(for: UIDevice.orientationDidChangeNotification).sink { [weak self] _ in
@ -174,6 +171,14 @@ open class DatePicker: EntryFieldBase {
popoverOverlayView.isHidden = true
}
open override func setDefaults() {
super.setDefaults()
selectedDate = nil
calendarModel = .init()
dateFormat = .shortNumeric
selectedDateLabel.textStyle = .bodyLarge
}
open override func getFieldContainer() -> UIView {
// stackview for controls in EntryFieldBase.controlContainerView
let controlStackView = UIStackView().with {
@ -200,12 +205,6 @@ open class DatePicker: EntryFieldBase {
calendarIcon.color = iconColorConfiguration.getColor(self)
}
/// Resets to default settings.
open override func reset() {
super.reset()
selectedDateLabel.textStyle = .bodyLarge
}
internal func formatDate(_ date: Date) {
let formatter = DateFormatter()
formatter.dateFormat = dateFormat.format

View File

@ -13,7 +13,7 @@ import Combine
/// A dropdown select is an expandable menu of predefined options that allows a customer to make a single selection.
@objcMembers
@objc(VDSDropdownSelect)
open class DropdownSelect: EntryFieldBase {
open class DropdownSelect: EntryFieldBase<String> {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@ -153,15 +153,23 @@ open class DropdownSelect: EntryFieldBase {
}()
// tap gesture
containerView
.publisher(for: UITapGestureRecognizer())
.sink { [weak self] _ in
self?.launchPicker()
}
.store(in: &subscribers)
containerView.onClick = { [weak self] _ in
self?.launchPicker()
}
containerView.height(44)
}
open override func setDefaults() {
super.setDefaults()
showInlineLabel = false
selectId = nil
inlineDisplayLabel.textStyle = .boldBodyLarge
selectedOptionLabel.textStyle = .bodyLarge
showInlineLabel = false
options = []
selectId = nil
}
open override func getFieldContainer() -> UIView {
let controlStackView = UIStackView().with {
$0.translatesAutoresizingMaskIntoConstraints = false
@ -188,17 +196,6 @@ open class DropdownSelect: EntryFieldBase {
selectedOptionLabel.surface = surface
selectedOptionLabel.isEnabled = isEnabled
}
/// Resets to default settings.
open override func reset() {
super.reset()
inlineDisplayLabel.textStyle = .boldBodyLarge
selectedOptionLabel.textStyle = .bodyLarge
showInlineLabel = false
options = []
selectId = nil
}
//--------------------------------------------------
// MARK: - Public Methods

View File

@ -402,17 +402,36 @@ open class ButtonIcon: Control, Changeable {
centerXConstraint?.activate()
centerYConstraint = icon.centerYAnchor.constraint(equalTo: iconLayoutGuide.centerYAnchor, constant: 0)
centerYConstraint?.activate()
publisher(for: .touchUpInside)
.sink(receiveValue: { [weak self] _ in
guard let self, isEnabled,
selectedIconName != nil,
selectable else { return }
toggle()
})
.store(in: &subscribers)
}
/// Executed on initialization for this View.
open override func initialSetup() {
super.initialSetup()
onClick = { control in
guard control.isEnabled else { return }
if control.selectedIconName != nil && control.selectable {
control.toggle()
}
}
open override func setDefaults() {
super.setDefaults()
badgeIndicatorModel = nil
kind = .ghost
surfaceType = .colorFill
iconName = nil
selectedIconName = nil
selectedIconColorConfiguration = nil
size = .large
floating = false
fitToIcon = false
hideBorder = true
showBadgeIndicator = false
selectable = false
iconOffset = .init(x: 0, y: 0)
customContainerSize = nil
customIconSize = nil
customBadgeIndicatorOffset = nil
onChange = nil
}
/// This will change the state of the Selector and execute the actionBlock if provided.
@ -422,26 +441,6 @@ open class ButtonIcon: Control, Changeable {
sendActions(for: .valueChanged)
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
kind = .ghost
surfaceType = .colorFill
size = .large
floating = false
hideBorder = true
iconOffset = .init(x: 0, y: 0)
iconName = nil
selectedIconName = nil
showBadgeIndicator = false
selectable = false
badgeIndicatorModel = nil
onChange = nil
shouldUpdateView = true
setNeedsUpdate()
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()

View File

@ -90,22 +90,29 @@ open class Icon: View {
addSubview(imageView)
imageView.pinToSuperView()
backgroundColor = .clear
isAccessibilityElement = true
accessibilityTraits = .none
accessibilityHint = "image"
}
open override func setDefaults() {
super.setDefaults()
backgroundColor = .clear
color = VDSColor.paletteBlack
size = .medium
name = nil
customSize = nil
imageView.image = nil
accessibilityHint = "image"
bridge_accessibilityLabelBlock = { [weak self] in
guard let self else { return "" }
return name?.rawValue ?? "icon"
}
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()
@ -123,12 +130,6 @@ open class Icon: View {
invalidateIntrinsicContentSize()
}
/// Resets to default settings.
open override func reset() {
super.reset()
color = VDSColor.paletteBlack
imageView.image = nil
}
}
extension UIImage {

View File

@ -0,0 +1,422 @@
//
// 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.'
@objcMembers
@objc(VDSInputStepper)
open class InputStepper: EntryFieldBase<Int> {
//--------------------------------------------------
// 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
var minWidth: CGFloat {
self == .large ? 121 : 90
}
var minHeight: CGFloat {
self == .large ? 44 : 32
}
var space: CGFloat {
self == .large ? VDSLayout.space3X : VDSLayout.space2X
}
var padding: CGFloat {
self == .large ? 6.0 : VDSLayout.space1X
}
var buttonContainerSize: Int {
self == .large ? 32 : 24
}
var textStyle: TextStyle {
self == .large ? .boldBodyLarge : .boldBodySmall
}
}
/// Enum used to describe the width of a fixed value or percentage of the input stepper control.
public enum ControlWidth {
case percentage(CGFloat)
case value(CGFloat)
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
/// If there is a width that is larger than this size's minimumWidth, the input stepper will resize to this width.
open var controlWidth: ControlWidth? {
get { _controlWidth }
set {
if let newValue {
switch newValue {
case .percentage(let percentage):
if percentage <= 100.0 {
_controlWidth = newValue
}
case .value(let value):
if value > 0 && value > containerSize.width {
_controlWidth = newValue
}
}
} else {
_controlWidth = nil
}
setNeedsUpdate()
}
}
/// Accepts percentage value to width of parent container.
open var widthPercentage: CGFloat? {
didSet {
if let percentage = widthPercentage, percentage > 100 {
widthPercentage = 100
}
setNeedsUpdate()
}
}
private var _defaultValue: Int = 0
open override var defaultValue: Int? {
get { _defaultValue }
set {
if let newValue {
_defaultValue = newValue > maxValue ? maxValue : newValue < minValue ? minValue : newValue
} else {
_defaultValue = 0
}
setNeedsUpdate()
}
}
open override var value: Int? { return defaultValue }
/// Maximum value of the input stepper, defaults to '99'.
lazy open var maxValue: Int = { _defaultMaxValue }() {
didSet {
if maxValue > _defaultMaxValue || maxValue < _defaultMinValue && maxValue > minValue {
maxValue = _defaultMaxValue
}
setNeedsUpdate()
}
}
/// Minimum value of the input stepper, defaults to '0'.
lazy open var minValue: Int = { _defaultMinValue }() {
didSet {
if minValue < _defaultMinValue && minValue >= _defaultMaxValue && minValue < maxValue {
minValue = _defaultMinValue
}
setNeedsUpdate()
}
}
/// The size of the input stepper. Defaults to 'large'.
open var size: Size = .large { didSet { setNeedsUpdate() } }
/// Accepts any text or character to appear next to input stepper value.
open var trailingText: String? { didSet { setNeedsUpdate() } }
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
private var _controlWidth: ControlWidth? = nil
private var _defaultMinValue: Int = 0
private var _defaultMaxValue: Int = 99
/// This is the view that will be wrapped with the border for userInteraction.
/// The only subview of this view is the stepperStackView.
internal var stepperContainerView = View().with {
$0.isAccessibilityElement = true
$0.accessibilityLabel = "Input Stepper"
}
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.iconName = Icon.Name(name: "minus")
$0.iconOffset = .init(x: -2, y: 0)
$0.customContainerSize = 32
$0.icon.customSize = 16
$0.backgroundColor = .clear
}
internal var 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
}
internal var textLabel = Label().with {
$0.setContentCompressionResistancePriority(.required, for: .vertical)
$0.textStyle = .boldBodyLarge
$0.numberOfLines = 1
$0.lineBreakMode = .byTruncatingTail
$0.textAlignment = .center
}
//--------------------------------------------------
// MARK: - Constraints
//--------------------------------------------------
internal var stepperWidthConstraint: NSLayoutConstraint?
internal var stepperHeightConstraint: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Configuration Properties
//--------------------------------------------------
internal override var containerSize: CGSize { CGSize(width: size.minWidth, height: size.minHeight) }
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
/// 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()
// Set initial states
defaultValue = 0
containerView.isEnabled = false
statusIcon.isHidden = true
//override the default settings since the containerView
//fieldStackView relationShip needs to be updated
//we are not applying spacing either in the edges since this
//is the view that will take place of the containerView for the
//design of the original "containerView". This will get refactored at
//some point.
fieldStackView.applyAlignment(.leading)
// Add listeners
decrementButton.onClick = { _ in self.decrementButtonClick() }
incrementButton.onClick = { _ in self.incrementButtonClick() }
// setting color config
textLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable()
}
open override func getFieldContainer() -> UIView {
stepperStackView.addArrangedSubview(decrementButton)
stepperStackView.addArrangedSubview(textLabel)
stepperStackView.addArrangedSubview(incrementButton)
// Set space between decrement button, label, and increment button relative to input Stepper size.
stepperStackView.setCustomSpacing(size.space, after: decrementButton)
stepperStackView.setCustomSpacing(size.space, after: textLabel)
// stepperContainerView for controls in EntryFieldBase.controlContainerView
stepperContainerView.addSubview(stepperStackView)
// Update Edge insets relative to input Stepper size.
stepperStackView.pinToSuperView(.uniform(size.padding))
stepperWidthConstraint = stepperContainerView.width(constant: containerSize.width, priority: .required)
stepperHeightConstraint = stepperContainerView.height(constant: containerSize.height, priority: .required)
return stepperContainerView
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()
statusIcon.isHidden = true
// Update label
textLabel.isEnabled = isEnabled
textLabel.surface = surface
textLabel.text = "\(_defaultValue) " + (trailingText ?? "")
textLabel.textStyle = size.textStyle
updateButtonStates()
}
open override var accessibilityElements: [Any]? {
get {
var elements = [Any]()
if !isReadOnly || isEnabled {
elements.append(contentsOf: [titleLabel, containerView, decrementButton, textLabel, incrementButton])
} else {
elements.append(contentsOf: [titleLabel, containerView, textLabel])
}
if showError {
if let errorText, !errorText.isEmpty {
elements.append(errorLabel)
}
}
if let helperText, !helperText.isEmpty {
elements.append(helperLabel)
}
return elements
}
set { super.accessibilityElements = newValue }
}
/// Resets to default settings.
open override func reset() {
super.reset()
textLabel.reset()
controlWidth = nil
widthPercentage = nil
defaultValue = 0
minValue = _defaultMinValue
maxValue = _defaultMaxValue
trailingText = nil
size = .large
}
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
override func updateContainerView() {
//we are not calling super since we
//are using the fieldStackView as the "containerView"
//which will get the look/feel of the containerView.
//this will get refactored in the future.
fieldStackView.backgroundColor = containerBackgroundColor
fieldStackView.layer.borderColor = borderColorConfiguration.getColor(self).cgColor
fieldStackView.layer.borderWidth = VDSFormControls.borderWidth
}
internal override func updateContainerWidth() {
//we are not calling super here since
//we are changing how the widths are getting calculated
//now by including a percentage.
defer {
fieldStackView.layer.cornerRadius = containerSize.height / 2
}
stepperWidthConstraint?.deactivate()
widthConstraint?.deactivate()
trailingLessThanEqualsConstraint?.deactivate()
trailingEqualsConstraint?.deactivate()
var widthConstraintConstant: CGFloat?
if let widthPercentage, let superWidth = horizontalPinnedWidth() {
// test value vs minimum width and take the greater value
widthConstraintConstant = max(superWidth * (widthPercentage / 100), minWidth)
} else if let width, width >= minWidth, width <= maxWidth {
widthConstraintConstant = width
} else if let parentWidth = width, parentWidth >= maxWidth {
widthConstraintConstant = maxWidth
} else if let parentWidth = width, parentWidth <= minWidth {
widthConstraintConstant = minWidth
}
if let widthConstraintConstant {
widthConstraint?.constant = widthConstraintConstant
widthConstraint?.activate()
trailingLessThanEqualsConstraint?.activate()
} else {
trailingEqualsConstraint?.activate()
}
// Update Edge insets if size changes applied.
stepperStackView.applyAlignment(.fill, edges: .uniform(size.padding))
// Update height if size changes applied.
stepperHeightConstraint?.constant = containerSize.height
//update the stepper's widthConstraint if
//controlWidth was set
guard let controlWidth else {
return
}
// Set the inputStepper's controlWidth based on percentage received relative to its parentView's frame.
let containerWidth: CGFloat = widthConstraintConstant ?? containerView.frame.size.width
var stepperWidthConstant: CGFloat?
var stepperWidth: CGFloat
switch controlWidth {
case .percentage(let percentage):
stepperWidth = max(containerWidth * ((percentage) / 100), minWidth)
case .value(let value):
stepperWidth = value
}
//get the value of the stepperWidthConstant
if stepperWidth >= containerSize.width && stepperWidth <= containerWidth {
stepperWidthConstant = stepperWidth
} else if stepperWidth >= containerWidth {
stepperWidthConstant = containerWidth
}
if let stepperWidthConstant {
stepperWidthConstraint?.constant = stepperWidthConstant
stepperWidthConstraint?.activate()
}
}
//--------------------------------------------------
// MARK: - Private Methods
//--------------------------------------------------
internal func decrementButtonClick() {
if _defaultValue > minValue {
defaultValue = _defaultValue - 1
sendActions(for: .valueChanged)
}
}
internal func incrementButtonClick() {
if _defaultValue < maxValue {
defaultValue = _defaultValue + 1
sendActions(for: .valueChanged)
}
}
internal func updateButtonStates() {
decrementButton.customContainerSize = size.buttonContainerSize
incrementButton.customContainerSize = size.buttonContainerSize
decrementButton.surface = surface
incrementButton.surface = surface
if isReadOnly || !isEnabled {
decrementButton.isEnabled = false
incrementButton.isEnabled = false
} else {
decrementButton.isEnabled = (defaultValue ?? _defaultMaxValue ) > minValue ? true : false
incrementButton.isEnabled = (defaultValue ?? _defaultMinValue) < maxValue ? true : false
}
}
}

View File

@ -0,0 +1,44 @@
MM/DD/YYYY
----------------
02/2024
----------------
- New component
02/15/2024
----------------
- Added Border align: Inside to Anatomy
- Removed leadingText property values from States.
- Added Read-only to States.
- Created a section for Minimum width in Layout and spacing and updated the minWidth to 145px.
- Added trailingText spacing to Layout and spacing.
- Reduced space between Button Icons and text to 12px in Layout and spacing.
- Added top/bottom padding values to Layout and spacing.
03/01/2024
----------------
- Removed Leading Text from “Content and other properties” section.
03/20/2024
----------------
- Updated Anatomy artwork and items
- Added width and controlWidth to Configurations
- Updated Width under Layout and spacing to show layout examples
04/12/2024
----------------
- Added a new configuration property (size) that includes large and small
- Reduced details from Anatomy page and added them to Configurations/Size
- Added Hit area, Small Input Stepper spacing properties to Layout and spacing
- Updated the Behavior page to display disabled button icon when value is at max and min
04/29/2024
----------------
- Updated the Behavior page to display disabled button icon when value is at max and min
05/10/2024
----------------
- Added helperTextPlacement property to Configurations
- Added Layout examples for right Helper Text placement in Layout and Spacing
- Added overflow examples in Overflow section of Layout and Spacing

View File

@ -192,42 +192,46 @@ open class Label: UILabel, ViewProtocol, UserInfoable {
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open func initialSetup() {
private func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
//register for ContentSizeChanges
NotificationCenter
.Publisher(center: .default, name: UIContentSizeCategory.didChangeNotification)
.sink { [weak self] notification in
self?.setNeedsUpdate()
}.store(in: &subscribers)
backgroundColor = .clear
numberOfLines = 0
lineBreakMode = .byTruncatingTail
translatesAutoresizingMaskIntoConstraints = false
accessibilityCustomActions = []
isAccessibilityElement = true
accessibilityTraits = .staticText
textAlignment = .left
shouldUpdateView = false
setup()
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
}
}
open func setup() {
//register for ContentSizeChanges
NotificationCenter
.Publisher(center: .default, name: UIContentSizeCategory.didChangeNotification)
.sink { [weak self] notification in
self?.setNeedsUpdate()
}.store(in: &subscribers)
translatesAutoresizingMaskIntoConstraints = false
isAccessibilityElement = true
}
open func reset() {
shouldUpdateView = false
open func setDefaults() {
backgroundColor = .clear
accessibilityTraits = .staticText
accessibilityCustomActions = []
surface = .light
isEnabled = true
attributes = nil
textStyle = .defaultStyle
lineBreakMode = .byTruncatingTail
textAlignment = .left
text = nil
attributedText = nil
numberOfLines = 0
backgroundColor = .clear
}
open func reset() {
shouldUpdateView = false
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
}

View File

@ -81,11 +81,6 @@ open class Line: View {
//--------------------------------------------------
// 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()
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()
@ -94,8 +89,8 @@ open class Line: View {
}
/// Resets to default settings.
open override func reset() {
super.reset()
open override func setDefaults() {
super.setDefaults()
style = .primary
orientation = .horizontal
}

View File

@ -274,20 +274,13 @@ open class Notification: View {
}
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
titleLabel.reset()
open override func setDefaults() {
super.setDefaults()
titleLabel.text = ""
titleLabel.textStyle = UIDevice.isIPad ? .boldBodyLarge : .boldBodySmall
subTitleLabel.reset()
subTitleLabel.textStyle = UIDevice.isIPad ? .bodyLarge : .bodySmall
buttonGroup.reset()
buttonGroup.alignment = .left
primaryButtonModel = nil
@ -302,9 +295,15 @@ open class Notification: View {
closeButton.name = .close
hideCloseButton = false
shouldUpdateView = true
setNeedsUpdate()
}
/// Resets to default settings.
open override func reset() {
titleLabel.reset()
subTitleLabel.reset()
buttonGroup.reset()
super.reset()
}
/// Used to make changes to the View based off a change events or from local properties.

View File

@ -95,8 +95,8 @@ open class Pagination: View {
// MARK: - Overrides
//--------------------------------------------------
/// Executed on initialization for this View.
open override func initialSetup() {
super.initialSetup()
open override func setup() {
super.setup()
collectionContainerView.addSubview(collectionView)
containerView.addSubview(previousButton)

View File

@ -60,8 +60,8 @@ open class PaginationButton: ButtonBase {
// MARK: - Overrides
//--------------------------------------------------
/// Executed on initialization for this View.
open override func initialSetup() {
super.initialSetup()
open override func setup() {
super.setup()
if #available(iOS 15.0, *) {
configuration = buttonConfiguration
} else {

View File

@ -67,17 +67,26 @@ open class RadioBoxGroup: SelectorGroupBase<RadioBoxItem>, SelectorGroupSingleSe
// MARK: - Overrides
//--------------------------------------------------
private func ensureDevice() {
var axis: NSLayoutConstraint.Axis = .vertical
var distribution: UIStackView.Distribution = .fill
defer {
mainStackView.axis = axis
mainStackView.distribution = distribution
}
if UIDevice.isIPad {
mainStackView.axis = .horizontal
mainStackView.distribution = .fillEqually
axis = .horizontal
distribution = .fillEqually
} else {
if UIDevice.current.orientation.isPortrait || UIDevice.current.orientation == .unknown {
mainStackView.axis = .vertical
mainStackView.distribution = .fill
} else {
mainStackView.axis = .horizontal
mainStackView.distribution = .fillEqually
guard let supportedOrientations = UIApplication.shared.windows.first?.rootViewController?.supportedInterfaceOrientations else {
return
}
let orientation = UIDevice.current.orientation
if supportedOrientations.contains(.landscape) && (orientation == .landscapeLeft || orientation == .landscapeRight) {
axis = .horizontal
distribution = .fillEqually
}
}
}

View File

@ -165,11 +165,38 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable {
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
/// Executed on initialization for this View.
open override func initialSetup() {
super.initialSetup()
onClick = { control in
control.toggle()
/// 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
selectorView.isAccessibilityElement = true
selectorView.accessibilityTraits = .button
addSubview(selectorView)
selectorView.isUserInteractionEnabled = false
selectorView.addSubview(selectorStackView)
selectorStackView.addArrangedSubview(selectorLeftLabelStackView)
selectorStackView.addArrangedSubview(subTextRightLabel)
selectorLeftLabelStackView.addArrangedSubview(textLabel)
selectorLeftLabelStackView.addArrangedSubview(subTextLabel)
selectorView
.pinTop()
.pinLeading()
.pinTrailing(0, .defaultHigh)
.pinBottom(0, .defaultHigh)
selectorStackView.pinToSuperView(.uniform(VDSLayout.space3X))
}
open override func setDefaults() {
super.setDefaults()
onClick = { [weak self] _ in
guard let self, isEnabled else { return }
toggle()
}
selectorView.bridge_accessibilityLabelBlock = { [weak self] in
@ -204,43 +231,7 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable {
return accessibilityLabels.joined(separator: ", ")
}
}
/// 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
selectorView.isAccessibilityElement = true
selectorView.accessibilityTraits = .button
addSubview(selectorView)
selectorView.isUserInteractionEnabled = false
selectorView.addSubview(selectorStackView)
selectorStackView.addArrangedSubview(selectorLeftLabelStackView)
selectorStackView.addArrangedSubview(subTextRightLabel)
selectorLeftLabelStackView.addArrangedSubview(textLabel)
selectorLeftLabelStackView.addArrangedSubview(subTextLabel)
selectorView
.pinTop()
.pinLeading()
.pinTrailing(0, .defaultHigh)
.pinBottom(0, .defaultHigh)
selectorStackView.pinToSuperView(.uniform(VDSLayout.space3X))
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
textLabel.reset()
subTextLabel.reset()
subTextRightLabel.reset()
textLabel.textStyle = .boldBodyLarge
subTextLabel.textStyle = .bodyLarge
subTextRightLabel.textStyle = .bodyLarge
@ -260,9 +251,14 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable {
isSelected = false
onChange = nil
}
shouldUpdateView = true
setNeedsUpdate()
/// Resets to default settings.
open override func reset() {
textLabel.reset()
subTextLabel.reset()
subTextRightLabel.reset()
super.reset()
}
/// This will change the state of the Selector and execute the actionBlock if provided.

View File

@ -77,12 +77,13 @@ open class RadioButtonGroup: SelectorGroupBase<RadioButtonItem>, SelectorGroupSi
}
}
open override func reset() {
super.reset()
open override func setDefaults() {
super.setDefaults()
inputId = nil
showError = false
}
public override func didSelect(_ selectedControl: RadioButtonItem) {
open override func didSelect(_ selectedControl: RadioButtonItem) {
if let selectedItem {
updateToggle(selectedItem)
}

View File

@ -92,8 +92,8 @@ open class Table: View {
//--------------------------------------------------
///Called upon initializing the table view
open override func initialSetup() {
super.initialSetup()
open override func setup() {
super.setup()
addSubview(matrixView)
matrixView.pinToSuperView()
}
@ -109,18 +109,16 @@ open class Table: View {
matrixView.collectionViewLayout.invalidateLayout()
}
/// Resets to default settings.
open override func reset() {
super.reset()
open override func setDefaults() {
super.setDefaults()
striped = false
padding = .standard
tableHeader = []
tableRows = []
fillContainer = true
columnWidths = nil
setNeedsUpdate()
}
func calculateColumnWidths() -> [CGFloat] {
guard let noOfColumns = tableData.first?.columnsCount else { return [] }
let itemWidth = floor(matrixView.safeAreaLayoutGuide.layoutFrame.width / CGFloat(noOfColumns))

View File

@ -221,10 +221,11 @@ open class Tabs: View {
super.layoutSubviews()
updateContentView()
}
open override func reset() {
super.reset()
shouldUpdateView = false
open override func setDefaults() {
super.setDefaults()
onTabDidSelect = nil
onTabShouldSelect = nil
orientation = .horizontal
borderLine = true
fillContainer = false
@ -235,11 +236,9 @@ open class Tabs: View {
selectedIndex = 0
size = .medium
sticky = false
tabViews.forEach{ $0.reset() }
shouldUpdateView = true
setNeedsUpdate()
tabModels = []
}
//--------------------------------------------------
// MARK: - Private Methods
//--------------------------------------------------

View File

@ -11,10 +11,7 @@ import VDSCoreTokens
import Combine
/// Base Class used to build out a Input controls.
@objcMembers
@objc(VDSEntryField)
open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
open class EntryFieldBase<ValueType>: Control, Changeable, FormFieldInternalValidatable {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@ -229,11 +226,11 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
open var inputId: String? { didSet { setNeedsUpdate() } }
/// The text of this textField.
open var value: String? {
open var value: ValueType? {
get { fatalError("must be read from subclass")}
}
open var defaultValue: AnyHashable? { didSet { setNeedsUpdate() } }
open var defaultValue: ValueType? { didSet { setNeedsUpdate() } }
open var isRequired: Bool = false { didSet { setNeedsUpdate() } }
@ -245,7 +242,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
}
}
open var rules = [AnyRule<String>]()
open var rules = [AnyRule<ValueType>]()
open var accessibilityHintText: String = "Double tap to open"
@ -316,6 +313,41 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
errorLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable()
helperLabel.textColorConfiguration = secondaryColorConfiguration.eraseToAnyColorable()
}
/// Updates the UI
open override func updateView() {
super.updateView()
updateRules()
updateContainerView()
updateContainerWidth()
updateTitleLabel()
updateErrorLabel()
updateHelperLabel()
}
open override func setDefaults() {
super.setDefaults()
titleLabel.textStyle = .bodySmall
errorLabel.textStyle = .bodySmall
helperLabel.textStyle = .bodySmall
labelText = nil
helperText = nil
showError = false
errorText = nil
tooltipModel = nil
transparentBackground = false
width = nil
inputId = nil
defaultValue = nil
isRequired = false
isReadOnly = false
helperTextPlacement = .bottom
rules = []
onChange = nil
containerView.bridge_accessibilityLabelBlock = { [weak self] in
guard let self else { return "" }
var accessibilityLabels = [String]()
@ -342,50 +374,23 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
}
containerView.bridge_accessibilityValueBlock = { [weak self] in
guard let self else { return "" }
return value
guard let self, let value else { return "" }
return "\(value)"
}
statusIcon.bridge_accessibilityLabelBlock = { [weak self] in
guard let self else { return "" }
return showError || hasInternalError ? "error" : nil
}
}
/// Updates the UI
open override func updateView() {
super.updateView()
updateRules()
updateContainerView()
updateContainerWidth()
updateTitleLabel()
updateErrorLabel()
updateHelperLabel()
}
/// Resets to default settings.
open override func reset() {
super.reset()
titleLabel.reset()
errorLabel.reset()
helperLabel.reset()
titleLabel.textStyle = .bodySmall
errorLabel.textStyle = .bodySmall
helperLabel.textStyle = .bodySmall
labelText = nil
helperText = nil
showError = false
errorText = nil
tooltipModel = nil
transparentBackground = false
width = nil
inputId = nil
defaultValue = nil
isRequired = false
isReadOnly = false
onChange = nil
super.reset()
}
open override var canBecomeFirstResponder: Bool {
@ -523,8 +528,8 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
//--------------------------------------------------
internal func updateRules() {
rules.removeAll()
if isRequired && useRequiredRule {
let rule = RequiredRule()
if isRequired && useRequiredRule && ValueType.self == String.self {
let rule = RequiredRule<ValueType>()
if let errorText, !errorText.isEmpty {
rule.errorMessage = errorText
} else if let labelText{
@ -542,7 +547,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
containerView.layer.borderWidth = VDSFormControls.borderWidth
containerView.layer.cornerRadius = VDSFormControls.borderRadius
}
internal func updateContainerWidth() {
widthConstraint?.deactivate()
trailingLessThanEqualsConstraint?.deactivate()

View File

@ -15,7 +15,7 @@ import Combine
/// dates and security codes in their correct formats.
@objcMembers
@objc(VDSInputField)
open class InputField: EntryFieldBase {
open class InputField: EntryFieldBase<String> {
//--------------------------------------------------
// MARK: - Initializers
@ -205,6 +205,22 @@ open class InputField: EntryFieldBase {
textField.textColorConfiguration = textFieldTextColorConfiguration
}
open override func getFieldContainer() -> UIView {
return textField
}
open override func setDefaults() {
super.setDefaults()
textField.text = ""
successLabel.textStyle = .bodySmall
fieldType = .text
showSuccess = false
successText = nil
containerView.bridge_accessibilityLabelBlock = { [weak self] in
guard let self else { return "" }
var accessibilityLabels = [String]()
@ -259,22 +275,10 @@ open class InputField: EntryFieldBase {
}
}
open override func getFieldContainer() -> UIView {
return textField
}
/// Resets to default settings.
open override func reset() {
super.reset()
textField.text = ""
successLabel.reset()
successLabel.textStyle = .bodySmall
fieldType = .text
showSuccess = false
successText = nil
helperTextPlacement = .bottom
super.reset()
}
/// Used to make changes to the View based off a change events or from local properties.

View File

@ -98,19 +98,22 @@ open class TextField: UITextField, ViewProtocol, Errorable {
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open func initialSetup() {
private func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
backgroundColor = .clear
translatesAutoresizingMaskIntoConstraints = false
setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
clipsToBounds = true
shouldUpdateView = false
setup()
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
}
}
open func setup() {
translatesAutoresizingMaskIntoConstraints = false
setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
clipsToBounds = true
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)
@ -124,6 +127,17 @@ open class TextField: UITextField, ViewProtocol, Errorable {
inputAccessoryView = accessView
}
open func setDefaults() {
backgroundColor = .clear
surface = .light
text = nil
formatText = nil
useScaledFont = false
showError = false
errorText = nil
textStyle = .defaultStyle
}
@objc func doneButtonAction() {
// Resigns the first responder status when 'Done' is tapped
let _ = resignFirstResponder()
@ -174,8 +188,7 @@ open class TextField: UITextField, ViewProtocol, Errorable {
open func reset() {
shouldUpdateView = false
surface = .light
text = nil
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
}

View File

@ -7,12 +7,14 @@
import Foundation
class RequiredRule: Rule {
class RequiredRule<ValueType>: Rule {
var maxLength: Int?
var errorMessage: String = "This field is required."
func isValid(value: String?) -> Bool {
guard let value, !value.isEmpty, value.count > 0 else { return false }
func isValid(value: ValueType?) -> Bool {
guard let value,
!"\(value)".isEmpty,
"\(value)".count > 0 else { return false }
return true
}
}

View File

@ -14,7 +14,7 @@ import Combine
/// Use a text area when you want customers to enter text thats longer than a single line.
@objcMembers
@objc(VDSTextArea)
open class TextArea: EntryFieldBase {
open class TextArea: EntryFieldBase<String> {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@ -166,14 +166,19 @@ open class TextArea: EntryFieldBase {
bottomContainerStackView.spacing = VDSLayout.space2X
}
open override func setDefaults() {
super.setDefaults()
minHeight = .twoX
maxLength = nil
textView.text = ""
characterCounterLabel.textStyle = .bodySmall
}
/// Resets to default settings.
open override func reset() {
super.reset()
textView.text = ""
characterCounterLabel.reset()
characterCounterLabel.textStyle = .bodySmall
setNeedsUpdate()
super.reset()
}
/// Used to make changes to the View based off a change events or from local properties.

View File

@ -107,17 +107,20 @@ open class TextView: UITextView, ViewProtocol, Errorable {
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
open func initialSetup() {
private func initialSetup() {
if !initialSetupPerformed {
initialSetupPerformed = true
backgroundColor = .clear
translatesAutoresizingMaskIntoConstraints = false
shouldUpdateView = false
setup()
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
}
}
open func setup() {
translatesAutoresizingMaskIntoConstraints = false
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)
@ -134,6 +137,15 @@ open class TextView: UITextView, ViewProtocol, Errorable {
placeholderLabel.pinToSuperView()
}
open func setDefaults() {
backgroundColor = .clear
surface = .light
text = nil
placeholder = nil
errorText = nil
showError = false
}
@objc func doneButtonAction() {
// Resigns the first responder status when 'Done' is tapped
resignFirstResponder()
@ -153,8 +165,7 @@ open class TextView: UITextView, ViewProtocol, Errorable {
open func reset() {
shouldUpdateView = false
surface = .light
text = nil
setDefaults()
shouldUpdateView = true
setNeedsUpdate()
}

View File

@ -44,7 +44,7 @@ open class TileContainer: TileContainerBase<TileContainer.Padding> {
}
}
open class TileContainerBase<PaddingType: DefaultValuing>: Control where PaddingType.ValueType == CGFloat {
open class TileContainerBase<PaddingType: DefaultValuing>: View where PaddingType.ValueType == CGFloat {
//--------------------------------------------------
// MARK: - Initializers
@ -118,6 +118,8 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
$0.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
}
private var isHighlighted: Bool = false { didSet { setNeedsUpdate() } }
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
@ -279,19 +281,18 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
return view
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
open override func setDefaults() {
super.setDefaults()
backgroundImage = nil
color = .white
aspectRatio = .none
backgroundEffect = .none
padding = .defaultValue
aspectRatio = .ratio1x1
imageFallbackColor = .light
width = nil
height = nil
showBorder = false
showDropShadow = false
shouldUpdateView = true
setNeedsUpdate()
}
/// Used to make changes to the View based off a change events or from local properties.
@ -337,6 +338,27 @@ open class TileContainerBase<PaddingType: DefaultValuing>: Control where Padding
set {}
}
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
if let onClickSubscriber {
isHighlighted = true
}
}
open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
if let onClickSubscriber {
isHighlighted = false
}
}
open override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesCancelled(touches, with: event)
if let onClickSubscriber {
isHighlighted = false
}
}
//--------------------------------------------------
// MARK: - Public Methods
//--------------------------------------------------

View File

@ -390,20 +390,19 @@ open class Tilelet: TileContainerBase<Tilelet.Padding> {
}
}
/// Resets to default settings.
open override func reset() {
shouldUpdateView = false
super.reset()
open override func setDefaults() {
super.setDefaults()
aspectRatio = .none
color = .black
textWidth = nil
textPostion = .top
//models
badgeModel = nil
titleModel = nil
subTitleModel = nil
descriptiveIconModel = nil
directionalIconModel = nil
shouldUpdateView = true
setNeedsUpdate()
}
/// Used to make changes to the View based off a change events or from local properties.

View File

@ -280,15 +280,12 @@ open class TitleLockup: View {
set {}
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
open override func setDefaults() {
super.setDefaults()
textAlignment = .left
eyebrowModel = nil
titleModel = nil
subTitleModel = nil
shouldUpdateView = true
setNeedsUpdate()
}
var labelViews = [UIView]()

View File

@ -55,6 +55,7 @@ open class Toggle: Control, Changeable, FormFieldable {
private var leftConstraints: [NSLayoutConstraint] = []
private var rightConstraints: [NSLayoutConstraint] = []
private var labelConstraints: [NSLayoutConstraint] = []
private var toggleConstraints: [NSLayoutConstraint] = []
//--------------------------------------------------
// MARK: - Configuration
@ -95,7 +96,7 @@ open class Toggle: Control, Changeable, FormFieldable {
open var toggleView = ToggleView().with {
$0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
$0.isUserInteractionEnabled = false
$0.isAccessibilityElement = false
$0.isAccessibilityElement = false
}
/// Used in showing the on/off text.
@ -148,35 +149,15 @@ open class Toggle: Control, Changeable, FormFieldable {
open var value: AnyHashable? { isOn }
/// The natural size for the receiving view, considering only properties of the view itself.
open override var intrinsicContentSize: CGSize {
if showLabel {
label.sizeToFit()
let size = CGSize(width: label.frame.width + spacingBetween + toggleContainerSize.width,
height: max(toggleContainerSize.height, label.frame.height))
return size
} else {
return toggleContainerSize
}
}
open override var shouldHighlight: Bool { false }
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
/// Executed on initialization for this View.
open override func initialSetup() {
super.initialSetup()
onClick = { control in
control.toggle()
}
}
//--------------------------------------------------
/// 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 = true
if #available(iOS 17.0, *) {
accessibilityTraits = .toggleButton
@ -208,6 +189,59 @@ open class Toggle: Control, Changeable, FormFieldable {
label.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor)
]
// Set content hugging priority
setContentHuggingPriority(.required, for: .horizontal)
isAccessibilityElement = true
if #available(iOS 17.0, *) {
accessibilityTraits = .toggleButton
} else {
accessibilityTraits = .button
}
addSubview(label)
addSubview(toggleView)
// Set up initial constraints for label and switch
toggleView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
//toggle
toggleConstraints = [
toggleView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor),
toggleView.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor)
]
//toggle and label variants
labelConstraints = [
height(constant: toggleContainerSize.height, priority: .defaultLow),
heightGreaterThanEqualTo(constant: toggleContainerSize.height, priority: .defaultHigh),
label.topAnchor.constraint(equalTo: topAnchor),
label.bottomAnchor.constraint(equalTo: bottomAnchor),
]
//label-toggle
leftConstraints = [
label.leadingAnchor.constraint(equalTo: leadingAnchor),
toggleView.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: spacingBetween),
toggleView.trailingAnchor.constraint(equalTo: trailingAnchor)
]
//toggle-label
rightConstraints = [
toggleView.leadingAnchor.constraint(equalTo: leadingAnchor),
label.leadingAnchor.constraint(equalTo: toggleView.trailingAnchor, constant: spacingBetween),
label.trailingAnchor.constraint(equalTo: trailingAnchor)
]
}
open override func setDefaults() {
super.setDefaults()
onClick = { [weak self] _ in
guard let self else { return }
toggle()
}
bridge_accessibilityValueBlock = { [weak self] in
guard let self else { return "" }
if showText {
@ -216,13 +250,7 @@ open class Toggle: Control, Changeable, FormFieldable {
return isSelected ? "On" : "Off"
}
}
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
label.reset()
isEnabled = true
isOn = false
isAnimated = true
@ -234,8 +262,12 @@ open class Toggle: Control, Changeable, FormFieldable {
textPosition = .left
inputId = nil
onChange = nil
shouldUpdateView = true
setNeedsUpdate()
}
/// Resets to default settings.
open override func reset() {
label.reset()
super.reset()
}
/// Used to make changes to the View based off a change events or from local properties.
@ -261,6 +293,8 @@ open class Toggle: Control, Changeable, FormFieldable {
label.isHidden = !showLabel
if showLabel {
NSLayoutConstraint.deactivate(toggleConstraints)
label.textAlignment = textPosition == .left ? .right : .left
label.textStyle = textStyle
label.text = statusText
@ -279,6 +313,7 @@ open class Toggle: Control, Changeable, FormFieldable {
NSLayoutConstraint.deactivate(leftConstraints)
NSLayoutConstraint.deactivate(rightConstraints)
NSLayoutConstraint.deactivate(labelConstraints)
NSLayoutConstraint.activate(toggleConstraints)
}
}
}

View File

@ -105,18 +105,10 @@ open class ToggleView: Control, Changeable, FormFieldable {
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
/// Executed on initialization for this View.
open override func initialSetup() {
super.initialSetup()
onClick = { control in
control.toggle()
}
}
/// 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 = true
if #available(iOS 17.0, *) {
accessibilityTraits = .toggleButton
@ -157,20 +149,21 @@ open class ToggleView: Control, Changeable, FormFieldable {
accessibilityLabel = "Toggle"
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
open override func setDefaults() {
super.setDefaults()
isOn = false
isAnimated = true
inputId = nil
toggleView.backgroundColor = toggleColorConfiguration.getColor(self)
knobView.backgroundColor = knobColorConfiguration.getColor(self)
onChange = nil
shouldUpdateView = true
setNeedsUpdate()
onChange = nil
onClick = { [weak self] _ in
guard let self else { return }
toggle()
}
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {
super.updateView()

View File

@ -159,18 +159,16 @@ open class Tooltip: Control, TooltipLaunchable {
}
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
size = .medium
title = ""
content = ""
fillColor = .primary
open override func setDefaults() {
super.setDefaults()
closeButtonText = "Close"
shouldUpdateView = true
setNeedsUpdate()
fillColor = .primary
size = .medium
title = nil
content = nil
contentView = nil
}
/// Used to make changes to the View based off a change events or from local properties.
open override func updateView() {

View File

@ -84,17 +84,13 @@ open class TrailingTooltipLabel: View, TooltipLaunchable {
}
}
/// Resets to default settings.
open override func reset() {
super.reset()
shouldUpdateView = false
open override func setDefaults() {
super.setDefaults()
labelText = nil
labelAttributes = nil
labelTextStyle = .defaultStyle
labelTextAlignment = .left
tooltipModel = nil
shouldUpdateView = true
setNeedsUpdate()
}
}

View File

@ -9,12 +9,12 @@ import Foundation
import UIKit
import Combine
public protocol Clickable: ViewProtocol where Self: UIControl {
public protocol Clickable: ViewProtocol {
/// Sets the primary Subscriber used for the UIControl event .touchUpInside.
var onClickSubscriber: AnyCancellable? { get set }
}
extension Clickable {
extension Clickable where Self: UIControl {
/// Allows the setting of a completion block against the onClickSubscriber cancellable. This will
/// completion block will get executed against the UIControl publisher for the 'touchUpInside' action.
public var onClick: ((Self) -> ())? {
@ -23,7 +23,7 @@ extension Clickable {
onClickSubscriber?.cancel()
if let newValue {
onClickSubscriber = publisher(for: .touchUpInside)
.sink { [weak self] c in
.sink { [weak self] c in
guard let self, self.isEnabled else { return }
newValue(c)
}
@ -34,3 +34,24 @@ extension Clickable {
}
}
}
extension Clickable where Self: UIView {
/// Allows the setting of a completion block against the onClickSubscriber cancellable. This will
/// completion block will get executed against the UIControl publisher for the 'touchUpInside' action.
public var onClick: ((Self) -> ())? {
get { return nil }
set {
onClickSubscriber?.cancel()
if let newValue {
onClickSubscriber = publisher(for: UITapGestureRecognizer())
.sink { [weak self] _ in
guard let self, self.isEnabled else { return }
newValue(self)
}
} else {
onClickSubscriber = nil
}
setNeedsUpdate()
}
}
}

View File

@ -8,7 +8,7 @@
import Foundation
/// Protocol used for a FormField object.
public protocol FormFieldable {
public protocol FormFieldable<ValueType> {
associatedtype ValueType = AnyHashable
/// Unique Id for the Form Field object within a Form.
@ -19,7 +19,7 @@ public protocol FormFieldable {
}
/// Protocol for FormFieldable that require internal validation.
public protocol FormFieldInternalValidatable: FormFieldable, Errorable {
public protocol FormFieldInternalValidatable<ValueType>: FormFieldable, Errorable {
/// Rules that drive the validator
var rules: [AnyRule<ValueType>] { get set }

View File

@ -19,12 +19,12 @@ public protocol ViewProtocol: AnyObject, Initable, Resettable, Enabling, Surface
/// Used for setting an implementation for the default Accessible Action
var accessibilityAction: ((Self) -> Void)? { get set }
/// Executed on initialization for this View.
func initialSetup()
/// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations.
func setup()
/// Default configurations for values and properties. This is called in the setup() and reset().
func setDefaults()
/// Used to make changes to the View based off a change events or from local properties.
func updateView()

View File

@ -33,6 +33,7 @@ Using the system allows designers and developers to collaborate more easily and
- ``CheckboxGroup``
- ``DropdownSelect``
- ``Icon``
- ``InputStepper``
- ``InputField``
- ``Label``
- ``Line``