diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 354969af..514e3fa0 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -1405,7 +1405,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; @@ -1442,7 +1442,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 38d087cc..0b29f4e2 100644 --- a/VDS/BaseClasses/Selector/SelectorBase.swift +++ b/VDS/BaseClasses/Selector/SelectorBase.swift @@ -114,9 +114,16 @@ open class SelectorBase: Control, SelectorControlable { accessibilityTraits = .button } + 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 9eee8f29..495ffaf3 100644 --- a/VDS/BaseClasses/Selector/SelectorItemBase.swift +++ b/VDS/BaseClasses/Selector/SelectorItemBase.swift @@ -190,7 +190,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/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/Notification/Notification.swift b/VDS/Components/Notification/Notification.swift index f91d45ef..241efb34 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.accessibilityText 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.accessibilityText + } + 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 b31bcb0e..18d3c26b 100644 --- a/VDS/Components/RadioBox/RadioBoxGroup.swift +++ b/VDS/Components/RadioBox/RadioBoxGroup.swift @@ -49,6 +49,8 @@ open class RadioBoxGroup: SelectorGroupBase, SelectorGroupSingleSe $0.isEnabled = !model.disabled $0.inputId = model.inputId $0.isSelected = model.selected + $0.strikethrough = model.strikethrough + $0.strikethroughAccessibilityText = model.strikethroughAccessibileText } } } @@ -115,12 +117,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 @@ -133,6 +137,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 c4105f42..23dd2b41 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? { didSet { setNeedsUpdate() } } @@ -183,7 +185,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. @@ -236,9 +238,8 @@ open class RadioBoxItem: Control, Changeable, FormFieldable { /// 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/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index 9f7cf49d..a8ad917a 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -129,6 +129,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 004b33ae..e34ee01c 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -151,7 +151,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 @@ -272,6 +273,25 @@ open class InputField: EntryFieldBase, UITextFieldDelegate { } } + /// Used to update any Accessibility properties. + open override func updateAccessibility() { + super.updateAccessibility() + textField.accessibilityLabel = showError ? "error" : nil + if showError { + accessibilityElements = [titleLabel, textField, icon, 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() + } } extension InputField.FieldType { diff --git a/VDS/Components/TextFields/TextArea/TextArea.swift b/VDS/Components/TextFields/TextArea/TextArea.swift index b8f0e009..d057333b 100644 --- a/VDS/Components/TextFields/TextArea/TextArea.swift +++ b/VDS/Components/TextFields/TextArea/TextArea.swift @@ -239,6 +239,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, icon, errorLabel, helperLabel] } else { @@ -246,6 +247,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/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 ----------------