diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index f805c10f..308c7f47 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -1377,7 +1377,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 59; + CURRENT_PROJECT_VERSION = 60; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1414,7 +1414,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 59; + CURRENT_PROJECT_VERSION = 60; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/VDS/BaseClasses/Selector/SelectorBase.swift b/VDS/BaseClasses/Selector/SelectorBase.swift index e74ca342..0b29f4e2 100644 --- a/VDS/BaseClasses/Selector/SelectorBase.swift +++ b/VDS/BaseClasses/Selector/SelectorBase.swift @@ -114,16 +114,16 @@ open class SelectorBase: Control, SelectorControlable { accessibilityTraits = .button } - /// Used to make changes to the View based off a change events or from local properties. open override func updateView() { super.updateView() setNeedsLayout() layoutIfNeeded() } - + /// Used to update any Accessibility properties.ß open override func updateAccessibility() { super.updateAccessibility() + accessibilityLabel = "\(Self.self)\(showError ? ", error" : "")" } /// This will change the state of the Selector and execute the actionBlock if provided. diff --git a/VDS/BaseClasses/Selector/SelectorItemBase.swift b/VDS/BaseClasses/Selector/SelectorItemBase.swift index 6a45e5e8..3dd31dda 100644 --- a/VDS/BaseClasses/Selector/SelectorItemBase.swift +++ b/VDS/BaseClasses/Selector/SelectorItemBase.swift @@ -192,7 +192,7 @@ open class SelectorItemBase: Control, Errorable, /// Used to update any Accessibility properties. open override func updateAccessibility() { super.updateAccessibility() - setAccessibilityLabel(for: [label, childLabel, errorLabel]) + setAccessibilityLabel(for: [selectorView, label, childLabel, errorLabel]) } /// Resets to default settings. diff --git a/VDS/Components/BadgeIndicator/BadgeIndicator.swift b/VDS/Components/BadgeIndicator/BadgeIndicator.swift index 89d886c6..a327270d 100644 --- a/VDS/Components/BadgeIndicator/BadgeIndicator.swift +++ b/VDS/Components/BadgeIndicator/BadgeIndicator.swift @@ -344,7 +344,6 @@ open class BadgeIndicator: View { label.isEnabled = isEnabled label.sizeToFit() setNeedsLayout() - layoutIfNeeded() } open override func updateAccessibility() { diff --git a/VDS/Components/Breadcrumbs/BreadcrumbCellItem.swift b/VDS/Components/Breadcrumbs/BreadcrumbCellItem.swift index 08a823fa..424ebcf3 100644 --- a/VDS/Components/Breadcrumbs/BreadcrumbCellItem.swift +++ b/VDS/Components/Breadcrumbs/BreadcrumbCellItem.swift @@ -84,7 +84,7 @@ final class BreadcrumbCellItem: UICollectionViewCell { separator.textColor = textColorConfiguration.getColor(surface) separator.isHidden = hideSlash self.breadCrumbItem = breadCrumbItem - layoutIfNeeded() + setNeedsLayout() } } diff --git a/VDS/Components/Checkbox/Checkbox.swift b/VDS/Components/Checkbox/Checkbox.swift index d9ca8a7e..550b6db1 100644 --- a/VDS/Components/Checkbox/Checkbox.swift +++ b/VDS/Components/Checkbox/Checkbox.swift @@ -42,7 +42,6 @@ open class Checkbox: SelectorBase { /// 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() - accessibilityLabel = "Checkbox" backgroundColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .selected) backgroundColorConfiguration.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: [.selected, .highlighted]) @@ -71,7 +70,7 @@ open class Checkbox: SelectorBase { isSelected.toggle() sendActions(for: .valueChanged) } - + open override func layoutSubviews() { super.layoutSubviews() diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index df67d431..febb12ca 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -230,7 +230,6 @@ open class Label: UILabel, ViewProtocol, UserInfoable { setNeedsDisplay() setNeedsLayout() - layoutIfNeeded() } open func updateAccessibility() { diff --git a/VDS/Components/Notification/Notification.swift b/VDS/Components/Notification/Notification.swift index be05bbf2..bd4df763 100644 --- a/VDS/Components/Notification/Notification.swift +++ b/VDS/Components/Notification/Notification.swift @@ -264,8 +264,6 @@ open class Notification: View { isAccessibilityElement = false accessibilityElements = [closeButton, typeIcon, titleLabel, subTitleLabel, buttonGroup] closeButton.accessibilityTraits = [.button] - closeButton.accessibilityLabel = "Close Notification" - } /// Resets to default settings. @@ -325,7 +323,6 @@ open class Notification: View { let iconColor = surface == .dark ? VDSColor.paletteWhite : VDSColor.paletteBlack typeIcon.name = style.iconName typeIcon.color = iconColor - typeIcon.accessibilityLabel = style.accessibleText closeButton.color = iconColor closeButton.isHidden = hideCloseButton } @@ -374,6 +371,12 @@ open class Notification: View { } } + open override func updateAccessibility() { + super.updateAccessibility() + closeButton.accessibilityLabel = "Close Notification" + typeIcon.accessibilityLabel = style.accessibleText + } + private func setConstraints() { labelViewAndButtonViewConstraint?.deactivate() labelViewBottomConstraint?.deactivate() diff --git a/VDS/Components/Pagination/Pagination.swift b/VDS/Components/Pagination/Pagination.swift index 11a5840d..f5a4e2c7 100644 --- a/VDS/Components/Pagination/Pagination.swift +++ b/VDS/Components/Pagination/Pagination.swift @@ -174,7 +174,7 @@ open class Pagination: View { let isNextAction = sender == nextButton _selectedPageIndex = if isNextAction { _selectedPageIndex + 1 } else { _selectedPageIndex - 1 } updateSelection() - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in guard let self else { return } UIAccessibility.post(notification: .announcement, argument: "Page \(self.selectedPage) of \(self.total) selected") } diff --git a/VDS/Components/Pagination/PaginationButton.swift b/VDS/Components/Pagination/PaginationButton.swift index 9e0eeb47..10745931 100644 --- a/VDS/Components/Pagination/PaginationButton.swift +++ b/VDS/Components/Pagination/PaginationButton.swift @@ -78,6 +78,11 @@ open class PaginationButton: ButtonBase { tintColor = color super.updateView() } + + open override func accessibilityActivate() -> Bool { + sendActions(for: .touchUpInside) + return true + } } extension PaginationButton { diff --git a/VDS/Components/RadioBox/RadioBoxGroup.swift b/VDS/Components/RadioBox/RadioBoxGroup.swift index 22e43426..e284840b 100644 --- a/VDS/Components/RadioBox/RadioBoxGroup.swift +++ b/VDS/Components/RadioBox/RadioBoxGroup.swift @@ -54,6 +54,8 @@ open class RadioBoxGroup: SelectorGroupBase, SelectorGroupSingleSe $0.inputId = model.inputId $0.hiddenValue = model.value $0.isSelected = model.selected + $0.strikethrough = model.strikethrough + $0.strikethroughAccessibilityText = model.strikethroughAccessibileText } } } @@ -120,12 +122,14 @@ extension RadioBoxGroup { /// Array of LabelAttributeModel objects used in rendering the subTextRight. public var subTextRightAttributes: [any LabelAttributeModel]? public var selected: Bool + public var strikethrough: Bool = false + public var strikethroughAccessibileText: String public init(disabled: Bool, surface: Surface = .light, inputId: String? = nil, value: AnyHashable? = nil, text: String = "", textAttributes: [any LabelAttributeModel]? = nil, subText: String? = nil, subTextAttributes: [any LabelAttributeModel]? = nil, subTextRight: String? = nil, subTextRightAttributes: [any LabelAttributeModel]? = nil, - selected: Bool = false, errorText: String? = nil, accessibileText: String? = nil) { + selected: Bool = false, errorText: String? = nil, accessibileText: String? = nil, strikethrough: Bool = false, strikethroughAccessibileText: String = "not available") { self.disabled = disabled self.surface = surface self.inputId = inputId @@ -138,6 +142,8 @@ extension RadioBoxGroup { self.subTextRightAttributes = subTextRightAttributes self.selected = selected self.accessibileText = accessibileText + self.strikethrough = strikethrough + self.strikethroughAccessibileText = strikethroughAccessibileText } public init() { diff --git a/VDS/Components/RadioBox/RadioBoxItem.swift b/VDS/Components/RadioBox/RadioBoxItem.swift index 7828ed3c..1a9d613f 100644 --- a/VDS/Components/RadioBox/RadioBoxItem.swift +++ b/VDS/Components/RadioBox/RadioBoxItem.swift @@ -38,7 +38,7 @@ open class RadioBoxItem: Control, Changeable, FormFieldable { $0.alignment = .top $0.distribution = .fill $0.axis = .horizontal - $0.spacing = 12 + $0.spacing = VDSLayout.space3X } private var selectorLeftLabelStackView = UIStackView().with { @@ -123,6 +123,8 @@ open class RadioBoxItem: Control, Changeable, FormFieldable { /// If provided, the radio box will be rendered to show the option with a strikethrough. open var strikethrough: Bool = false { didSet { setNeedsUpdate() } } + open var strikethroughAccessibilityText: String = "not available" { didSet { setNeedsUpdate() } } + open var inputId: String? { didSet { setNeedsUpdate() } } open var value: AnyHashable? { hiddenValue } @@ -185,7 +187,7 @@ open class RadioBoxItem: Control, Changeable, FormFieldable { .pinTrailing(0, .defaultHigh) .pinBottom(0, .defaultHigh) - selectorStackView.pinToSuperView(.uniform(16)) + selectorStackView.pinToSuperView(.uniform(VDSLayout.space3X)) } /// Resets to default settings. @@ -233,15 +235,13 @@ open class RadioBoxItem: Control, Changeable, FormFieldable { updateLabels() setNeedsLayout() - layoutIfNeeded() } /// Used to update any Accessibility properties. open override func updateAccessibility() { super.updateAccessibility() - if accessibilityLabel == nil { - setAccessibilityLabel(for: [textLabel, subTextLabel, subTextRightLabel]) - } + setAccessibilityLabel(for: [textLabel, subTextLabel, subTextRightLabel]) + accessibilityValue = strikethrough ? strikethroughAccessibilityText : nil } //-------------------------------------------------- diff --git a/VDS/Components/Tabs/Tab.swift b/VDS/Components/Tabs/Tab.swift index af095d85..e96e20d6 100644 --- a/VDS/Components/Tabs/Tab.swift +++ b/VDS/Components/Tabs/Tab.swift @@ -172,7 +172,6 @@ extension Tabs { label.textAlignment = textAlignment.value label.textColorConfiguration = textColorConfiguration.eraseToAnyColorable() setNeedsLayout() - layoutIfNeeded() } /// Used to update any Accessibility properties. diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index 54851202..bc7d73e2 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -135,6 +135,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { open var errorLabel = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) $0.textStyle = .bodySmall + $0.accessibilityValue = "error" } open var helperLabel = Label().with { diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index db722165..62b361c6 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -144,7 +144,8 @@ open class InputField: EntryFieldBase, UITextFieldDelegate { /// 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 + minWidthConstraint = containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: 0) minWidthConstraint?.isActive = true @@ -335,6 +336,26 @@ open class InputField: EntryFieldBase, UITextFieldDelegate { tooltipModel = toolTipModel } + /// Used to update any Accessibility properties. + open override func updateAccessibility() { + super.updateAccessibility() + textField.accessibilityLabel = showError ? "error" : nil + if showError { + accessibilityElements = [titleLabel, textField, statusIcon, errorLabel, helperLabel] + } else { + accessibilityElements = [titleLabel, textField, helperLabel] + } + } + + open override var canBecomeFirstResponder: Bool { true } + + open override func resignFirstResponder() -> Bool { + if textField.isFirstResponder { + textField.resignFirstResponder() + } + return super.resignFirstResponder() + } + //-------------------------------------------------- // MARK: - Password //-------------------------------------------------- diff --git a/VDS/Components/TextFields/TextArea/TextArea.swift b/VDS/Components/TextFields/TextArea/TextArea.swift index 1de4c1aa..d1924b2f 100644 --- a/VDS/Components/TextFields/TextArea/TextArea.swift +++ b/VDS/Components/TextFields/TextArea/TextArea.swift @@ -225,6 +225,7 @@ open class TextArea: EntryFieldBase { /// Used to update any Accessibility properties. open override func updateAccessibility() { super.updateAccessibility() + textView.accessibilityLabel = showError ? "error" : nil if showError { accessibilityElements = [titleLabel, textView, statusIcon, errorLabel, helperLabel] } else { @@ -232,6 +233,15 @@ open class TextArea: EntryFieldBase { } } + open override var canBecomeFirstResponder: Bool { true } + + open override func resignFirstResponder() -> Bool { + if textView.isFirstResponder { + textView.resignFirstResponder() + } + return super.resignFirstResponder() + } + //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- diff --git a/VDS/Components/TextFields/TextArea/TextView.swift b/VDS/Components/TextFields/TextArea/TextView.swift index d461ee23..c35b4a80 100644 --- a/VDS/Components/TextFields/TextArea/TextView.swift +++ b/VDS/Components/TextFields/TextArea/TextView.swift @@ -147,5 +147,6 @@ open class TextView: UITextView, ViewProtocol { attributedText = nil } } + } diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index ab053ce7..bdf19831 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -329,12 +329,14 @@ open class TileContainerBase: Control where Padding widthConstraint?.isActive = false heightConstraint?.isActive = false } + + applyBackgroundEffects() + if showDropShadow, surface == .light { addDropShadow(dropShadowConfiguration) } else { removeDropShadows() } - applyBackgroundEffects() } /// Used to update frames for the added CAlayers to our view diff --git a/VDS/Components/Tilelet/Tilelet.swift b/VDS/Components/Tilelet/Tilelet.swift index e79f36f0..fb0e5cf8 100644 --- a/VDS/Components/Tilelet/Tilelet.swift +++ b/VDS/Components/Tilelet/Tilelet.swift @@ -403,7 +403,7 @@ open class Tilelet: TileContainerBase { if width != nil && (aspectRatio != .none || height != nil) { updateTextPositionAlignment() } - layoutIfNeeded() + setNeedsLayout() } /// Used to update any Accessibility properties. diff --git a/VDS/Components/Toggle/ToggleView.swift b/VDS/Components/Toggle/ToggleView.swift index 8faf4def..230e9c82 100644 --- a/VDS/Components/Toggle/ToggleView.swift +++ b/VDS/Components/Toggle/ToggleView.swift @@ -227,7 +227,6 @@ open class ToggleView: Control, Changeable, FormFieldable { knobTrailingConstraint?.isActive = true knobLeadingConstraint?.isActive = true setNeedsLayout() - layoutIfNeeded() } private func updateToggle() { diff --git a/VDS/Extensions/DispatchQueue+Once.swift b/VDS/Extensions/DispatchQueue+Once.swift index 092500cf..15c96e41 100644 --- a/VDS/Extensions/DispatchQueue+Once.swift +++ b/VDS/Extensions/DispatchQueue+Once.swift @@ -8,7 +8,7 @@ import Foundation extension DispatchQueue { - private static var _onceTracker = [String]() + private static var _onceTracker = Set() public class func once( file: String = #file, @@ -31,12 +31,17 @@ extension DispatchQueue { token: String, block: () -> Void ) { + // Peek ahead to avoid the intersection. + guard !_onceTracker.contains(token) else { return } + objc_sync_enter(self) defer { objc_sync_exit(self) } + // Double check we are the first in the critical section. guard !_onceTracker.contains(token) else { return } - _onceTracker.append(token) + // Execute. + _onceTracker.insert(token) block() } } diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index d9087a9b..855ef058 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -3,6 +3,10 @@ - CXTDT-544442 - Button Icon - Selected state needs to allow custom color - CXTDT-546821 - TextArea - Accessibility - input field is not receiving swipe focus - CXTDT-547200 - Carousel scrollbar – Accessibility - The scrollbar is receiving right/ left swipe focus +- CXTDT-549888 - Pagination - Accessibility - Next/Previous +- CXTDT-542333 - RadioBox Padding +- CXTDT-549901 - RadioBox strike through state +- CXTDT-546824 - Notification - Accessibility - Improper label for close notification button & icon 1.0.59 ----------------