diff --git a/VDS/Components/BadgeIndicator/BadgeIndicator.swift b/VDS/Components/BadgeIndicator/BadgeIndicator.swift index a327270d..aae927a8 100644 --- a/VDS/Components/BadgeIndicator/BadgeIndicator.swift +++ b/VDS/Components/BadgeIndicator/BadgeIndicator.swift @@ -210,6 +210,7 @@ open class BadgeIndicator: View { /// The Container's height. open var height: CGFloat? { didSet { setNeedsUpdate() } } + open var accessibilityText: String? { didSet { setNeedsUpdate() } } //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- @@ -348,7 +349,9 @@ open class BadgeIndicator: View { open override func updateAccessibility() { super.updateAccessibility() - if kind == .numbered { + if let accessibilityText { + accessibilityLabel = accessibilityText + } else if kind == .numbered { accessibilityLabel = label.text } else { accessibilityLabel = "Simple" diff --git a/VDS/Components/DropdownSelect/DropdownSelect.swift b/VDS/Components/DropdownSelect/DropdownSelect.swift index e86b3792..80f94988 100644 --- a/VDS/Components/DropdownSelect/DropdownSelect.swift +++ b/VDS/Components/DropdownSelect/DropdownSelect.swift @@ -102,8 +102,6 @@ open class DropdownSelect: EntryFieldBase { /// 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 = "Dropdown Select" // stackview for controls in EntryFieldBase.controlContainerView let controlStackView = UIStackView().with { @@ -118,6 +116,10 @@ open class DropdownSelect: EntryFieldBase { controlStackView.addArrangedSubview(inlineDisplayLabel) controlStackView.addArrangedSubview(selectedOptionLabel) + containerStackView.isAccessibilityElement = true + containerStackView.accessibilityLabel = "Dropdown Select" + inlineDisplayLabel.isAccessibilityElement = true + controlStackView.setCustomSpacing(0, after: dropdownField) controlStackView.setCustomSpacing(VDSLayout.space1X, after: inlineDisplayLabel) controlStackView.setCustomSpacing(VDSLayout.space3X, after: selectedOptionLabel) @@ -245,9 +247,41 @@ open class DropdownSelect: EntryFieldBase { statusIcon.color = iconColorConfiguration.getColor(self) } + open override func updateAccessibility() { + super.updateAccessibility() + var selectedOption = selectedOptionLabel.text ?? "" + containerStackView.accessibilityLabel = "Dropdown Select, \(selectedOption) \(isReadOnly ? ", read only" : "")" + containerStackView.accessibilityHint = isReadOnly || !isEnabled ? "" : "Double tap to open." + } + + open override var accessibilityElements: [Any]? { + get { + var elements = [Any]() + elements.append(contentsOf: [titleLabel, containerStackView]) + + if showError { + elements.append(statusIcon) + if let errorText, !errorText.isEmpty { + elements.append(errorLabel) + } + } + + if let helperText, !helperText.isEmpty { + elements.append(helperLabel) + } + + return elements + } + + set { super.accessibilityElements = newValue } + } + + @objc open func pickerDoneClicked() { optionsPicker.isHidden = true dropdownField.resignFirstResponder() + setNeedsUpdate() + UIAccessibility.post(notification: .layoutChanged, argument: containerStackView) } } @@ -258,6 +292,7 @@ extension DropdownSelect: UIPickerViewDelegate, UIPickerViewDataSource { internal func launchPicker() { if optionsPicker.isHidden { + UIAccessibility.post(notification: .layoutChanged, argument: optionsPicker) dropdownField.becomeFirstResponder() } else { dropdownField.resignFirstResponder() diff --git a/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift b/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift index b97a1fac..5ba6a9fe 100644 --- a/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift +++ b/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift @@ -544,6 +544,7 @@ open class ButtonIcon: Control, Changeable { badgeIndicator.horizontalPadding = badgeIndicatorModel.horizontalPadding badgeIndicator.hideDot = badgeIndicatorModel.hideDot badgeIndicator.hideBorder = badgeIndicatorModel.hideBorder + badgeIndicator.accessibilityText = badgeIndicatorModel.accessibilityText } private func updateExpandDirectionalConstraints() { diff --git a/VDS/Components/Icon/ButtonIcon/ButtonIconBadgeIndicatorModel.swift b/VDS/Components/Icon/ButtonIcon/ButtonIconBadgeIndicatorModel.swift index e1c04b23..28d731b0 100644 --- a/VDS/Components/Icon/ButtonIcon/ButtonIconBadgeIndicatorModel.swift +++ b/VDS/Components/Icon/ButtonIcon/ButtonIconBadgeIndicatorModel.swift @@ -46,6 +46,9 @@ extension ButtonIcon { /// Trailing Text height that will be used for the badge indicator. public var trailingText: String? + /// Accessibliity Text + public var accessibilityText: String? + /// Dot Size that will be used for the badge indicator. public var dotSize: CGFloat? @@ -61,7 +64,7 @@ extension ButtonIcon { /// Hide Border that will be used for the badge indicator. public var hideBorder: Bool = false - public init(kind: BadgeIndicator.Kind = .simple, fillColor: BadgeIndicator.FillColor = .red, expandDirection: ExpandDirection = .right, size: BadgeIndicator.Size = .xxlarge, maximumDigits: BadgeIndicator.MaximumDigits = .two, width: CGFloat? = nil, height: CGFloat? = nil, number: Int? = nil, leadingCharacter: String? = "", trailingText: String? = "", dotSize: CGFloat? = nil, verticalPadding: CGFloat? = nil, horizontalPadding: CGFloat? = nil, hideDot: Bool = false, hideBorder: Bool = false) { + public init(kind: BadgeIndicator.Kind = .simple, fillColor: BadgeIndicator.FillColor = .red, expandDirection: ExpandDirection = .right, size: BadgeIndicator.Size = .xxlarge, maximumDigits: BadgeIndicator.MaximumDigits = .two, width: CGFloat? = nil, height: CGFloat? = nil, number: Int? = nil, leadingCharacter: String? = "", trailingText: String? = "", accessibilityText: String? = nil, dotSize: CGFloat? = nil, verticalPadding: CGFloat? = nil, horizontalPadding: CGFloat? = nil, hideDot: Bool = false, hideBorder: Bool = false) { self.kind = kind self.fillColor = fillColor self.expandDirection = expandDirection @@ -70,6 +73,7 @@ extension ButtonIcon { self.width = width self.height = height self.number = number + self.accessibilityText = accessibilityText self.leadingCharacter = leadingCharacter self.trailingText = trailingText self.dotSize = dotSize diff --git a/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift b/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift index 20666b10..6b03d103 100644 --- a/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift +++ b/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift @@ -77,7 +77,7 @@ public class TooltipLabelAttribute: ActionLabelAttributeModel, TooltipLaunchable self.subscriber = subscriber self.surface = surface self.model = model - self.accessibleText = accessibleText + self.accessibleText = accessibleText ?? model.accessibleText self.presenter = presenter //create the tooltip click event diff --git a/VDS/Components/Notification/Notification.swift b/VDS/Components/Notification/Notification.swift index bd4df763..b1010176 100644 --- a/VDS/Components/Notification/Notification.swift +++ b/VDS/Components/Notification/Notification.swift @@ -104,6 +104,7 @@ open class Notification: View { open var typeIcon = Icon().with { $0.name = .infoBold $0.size = UIDevice.isIPad ? .medium : .small + $0.accessibilityTraits.remove(.image) } /// Icon used for the close. diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index 644a8de1..5c549564 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -207,7 +207,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { open override func setup() { super.setup() - isAccessibilityElement = true + isAccessibilityElement = false addSubview(stackView) //create the wrapping view diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index 640538fe..eb67c255 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -148,7 +148,6 @@ open class InputField: EntryFieldBase { /// 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 @@ -373,11 +372,29 @@ open class InputField: EntryFieldBase { 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 accessibilityElements: [Any]? { + get { + var elements = [Any]() + elements.append(contentsOf: [titleLabel, textField]) + if showError { + elements.append(statusIcon) + if let errorText, !errorText.isEmpty { + elements.append(errorLabel) + } + } else if showSuccess, let successText, !successText.isEmpty { + elements.append(successLabel) + } + + if let helperText, !helperText.isEmpty { + elements.append(helperLabel) + } + + return elements } + + set { super.accessibilityElements = newValue } } open override var canBecomeFirstResponder: Bool { true } diff --git a/VDS/Components/TextFields/InputField/TextField.swift b/VDS/Components/TextFields/InputField/TextField.swift index 14178020..9a96829e 100644 --- a/VDS/Components/TextFields/InputField/TextField.swift +++ b/VDS/Components/TextFields/InputField/TextField.swift @@ -10,6 +10,25 @@ import UIKit @objc(VDSTextField) open class TextField: UITextField { + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + required public init() { + super.init(frame: .zero) + initialSetup() + } + + public override init(frame: CGRect) { + super.init(frame: .zero) + initialSetup() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + initialSetup() + } + var horizontalPadding: CGFloat = 0 open override func textRect(forBounds bounds: CGRect) -> CGRect { @@ -35,6 +54,25 @@ open class TextField: UITextField { } } + open func initialSetup() { + let doneToolbar: UIToolbar = UIToolbar() + doneToolbar.translatesAutoresizingMaskIntoConstraints = false + doneToolbar.barStyle = .default + + let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self.doneButtonAction)) + done.accessibilityHint = "Double tap to finish editing." + doneToolbar.items = [flexSpace, done] + doneToolbar.sizeToFit() + + inputAccessoryView = doneToolbar + } + + @objc func doneButtonAction() { + // Resigns the first responder status when 'Done' is tapped + resignFirstResponder() + } + open override func becomeFirstResponder() -> Bool { let success = super.becomeFirstResponder() if isSecureTextEntry, let text { diff --git a/VDS/Components/TextFields/TextArea/TextArea.swift b/VDS/Components/TextFields/TextArea/TextArea.swift index 7229c4a9..5ea944a3 100644 --- a/VDS/Components/TextFields/TextArea/TextArea.swift +++ b/VDS/Components/TextFields/TextArea/TextArea.swift @@ -161,7 +161,6 @@ open class TextArea: EntryFieldBase { /// 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 containerStackView.pinToSuperView(.uniform(VDSFormControls.spaceInset)) minWidthConstraint = containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: containerSize.width) minWidthConstraint?.isActive = true @@ -264,13 +263,31 @@ open class TextArea: EntryFieldBase { open override func updateAccessibility() { super.updateAccessibility() textView.accessibilityLabel = showError ? "error" : nil - if showError { - accessibilityElements = [titleLabel, textView, statusIcon, errorLabel, helperLabel] - } else { - accessibilityElements = [titleLabel, textView, helperLabel] - } } + open override var accessibilityElements: [Any]? { + get { + var elements = [Any]() + elements.append(contentsOf: [titleLabel, textView]) + + if showError { + elements.append(statusIcon) + if let errorText, !errorText.isEmpty { + elements.append(errorLabel) + } + } + + if let helperText, !helperText.isEmpty { + elements.append(helperLabel) + } + + return elements + } + + set { super.accessibilityElements = newValue } + } + + open override var canBecomeFirstResponder: Bool { true } open override func resignFirstResponder() -> Bool { diff --git a/VDS/Components/TextFields/TextArea/TextView.swift b/VDS/Components/TextFields/TextArea/TextView.swift index c35b4a80..efe8164b 100644 --- a/VDS/Components/TextFields/TextArea/TextView.swift +++ b/VDS/Components/TextFields/TextArea/TextView.swift @@ -97,7 +97,6 @@ open class TextView: UITextView, ViewProtocol { initialSetupPerformed = true backgroundColor = .clear translatesAutoresizingMaskIntoConstraints = false - accessibilityCustomActions = [] setup() setNeedsUpdate() } @@ -106,8 +105,25 @@ open class TextView: UITextView, ViewProtocol { open func setup() { translatesAutoresizingMaskIntoConstraints = false + let doneToolbar: UIToolbar = UIToolbar() + doneToolbar.translatesAutoresizingMaskIntoConstraints = false + doneToolbar.barStyle = .default + + let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self.doneButtonAction)) + done.accessibilityHint = "Double tap to finish editing." + doneToolbar.items = [flexSpace, done] + doneToolbar.sizeToFit() + + inputAccessoryView = doneToolbar + } + @objc func doneButtonAction() { + // Resigns the first responder status when 'Done' is tapped + resignFirstResponder() + } + open func updateView() { updateLabel() } @@ -118,7 +134,6 @@ open class TextView: UITextView, ViewProtocol { shouldUpdateView = false surface = .light text = nil - accessibilityCustomActions = [] shouldUpdateView = true setNeedsUpdate() } diff --git a/VDS/Components/Tooltip/TooltipDialog.swift b/VDS/Components/Tooltip/TooltipDialog.swift index 9822baae..a9ee3714 100644 --- a/VDS/Components/Tooltip/TooltipDialog.swift +++ b/VDS/Components/Tooltip/TooltipDialog.swift @@ -48,7 +48,6 @@ open class TooltipDialog: View, UIScrollViewDelegate { lazy var primaryAccessibilityElement = UIAccessibilityElement(accessibilityContainer: self).with { $0.accessibilityLabel = "Modal" - $0.accessibilityFrameInContainerSpace = .init(origin: .zero, size: .init(width: fullWidth, height: VDSLayout.space1X)) } //-------------------------------------------------- @@ -222,7 +221,8 @@ open class TooltipDialog: View, UIScrollViewDelegate { super.updateAccessibility() primaryAccessibilityElement.accessibilityHint = "Double tap on the \(tooltipModel.closeButtonText) button to close." - + primaryAccessibilityElement.accessibilityFrameInContainerSpace = .init(origin: .zero, size: frame.size) + var elements: [Any] = [primaryAccessibilityElement] contentStackView.arrangedSubviews.forEach{ elements.append($0) } elements.append(closeButton) diff --git a/VDS/Components/Tooltip/TooltipModel.swift b/VDS/Components/Tooltip/TooltipModel.swift index ee690d74..461fda66 100644 --- a/VDS/Components/Tooltip/TooltipModel.swift +++ b/VDS/Components/Tooltip/TooltipModel.swift @@ -17,16 +17,19 @@ extension Tooltip { public var title: String? public var content: String? public var contentView: UIView? + public var accessibleText: String? public var contentViewAlignment: UIStackView.Alignment? public init(closeButtonText: String = "Close", title: String? = nil, content: String? = nil, contentView: UIView? = nil, + accessibleText: String? = "Tooltip", contentViewAlignment: UIStackView.Alignment = .leading) { self.closeButtonText = closeButtonText self.title = title self.content = content self.contentView = contentView + self.accessibleText = accessibleText self.contentViewAlignment = contentViewAlignment } } diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 7cea34d5..258951db 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,3 +1,8 @@ +1.0.62 +---------------- +- CXTDT-546824 - Notification - Accessibility - Redundant text is provided for the notification icon. +- CXTDT-553663 - DropdownSelect - Accessibility - 5 issues + 1.0.61 ---------------- - CXTDT-552068 - Text Area - Text padding