diff --git a/VDS/BaseClasses/Control.swift b/VDS/BaseClasses/Control.swift index 274d7d9b..6d212e22 100644 --- a/VDS/BaseClasses/Control.swift +++ b/VDS/BaseClasses/Control.swift @@ -47,6 +47,8 @@ open class Control: UIControl, ViewProtocol, UserInfoable, Clickable { //-------------------------------------------------- open var shouldUpdateView: Bool = true + open var shouldUpdateAccessibility: Bool = true + open var userInfo = [String: Primitive]() open var surface: Surface = .light { didSet { setNeedsUpdate() } } diff --git a/VDS/BaseClasses/Selector/SelectorBase.swift b/VDS/BaseClasses/Selector/SelectorBase.swift index 4ea3ebd3..f8c9650d 100644 --- a/VDS/BaseClasses/Selector/SelectorBase.swift +++ b/VDS/BaseClasses/Selector/SelectorBase.swift @@ -124,6 +124,7 @@ open class SelectorBase: Control, SelectorControlable { open override func updateAccessibility() { super.updateAccessibility() accessibilityLabel = "\(Self.self)\(showError ? ", error" : "")" + accessibilityHint = !isEnabled ? "" : "Double tap to open." } /// 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 35c77922..555102a2 100644 --- a/VDS/BaseClasses/Selector/SelectorItemBase.swift +++ b/VDS/BaseClasses/Selector/SelectorItemBase.swift @@ -147,27 +147,51 @@ open class SelectorItemBase: Control, Errorable, open var accessibilityValueText: String? + open var accessibilityLabelText: String { + var accessibilityLabels = [String]() + accessibilityLabels.append("\(Selector.self)") + + if let text = labelText, !text.isEmpty { + accessibilityLabels.append(text) + } + + if let text = childText, !text.isEmpty { + accessibilityLabels.append(text) + } + + if !isEnabled { + accessibilityLabels.append("dimmed") + } + + if let errorText, showError, !errorText.isEmpty { + accessibilityLabels.append("error, \(errorText)") + } + + return accessibilityLabels.joined(separator: ", ") + } + //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- /// Executed on initialization for this View. open override func initialSetup() { super.initialSetup() - onClick = { control in - control.toggle() - } + onClick = { [weak self] control in + self?.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() - selectorView.isAccessibilityElement = false - isAccessibilityElement = true - accessibilityTraits = .button - addSubview(mainStackView) - mainStackView.isUserInteractionEnabled = false + selectorView.isAccessibilityElement = true + selectorView.shouldUpdateAccessibility = false + isAccessibilityElement = false + addSubview(mainStackView) + + mainStackView.isUserInteractionEnabled = false mainStackView.addArrangedSubview(selectorStackView) mainStackView.addArrangedSubview(errorLabel) selectorStackView.addArrangedSubview(selectorView) @@ -185,6 +209,7 @@ open class SelectorItemBase: Control, Errorable, open override func updateView() { super.updateView() updateLabels() + selectorView.isUserInteractionEnabled = true selectorView.showError = showError selectorView.isSelected = isSelected selectorView.isHighlighted = isHighlighted @@ -195,10 +220,35 @@ open class SelectorItemBase: Control, Errorable, /// Used to update any Accessibility properties. open override func updateAccessibility() { super.updateAccessibility() - setAccessibilityLabel(for: [selectorView, label, childLabel, errorLabel]) + selectorView.accessibilityLabel = accessibilityLabelText + selectorView.accessibilityHint = !isEnabled ? "" : "Double tap to activate." accessibilityValue = accessibilityValueText } + open override var accessibilityElements: [Any]? { + get { + var elements = [Any]() + + elements.append(selectorView) + + if let text = labelText, !text.isEmpty { + elements.append(label) + } + + if let text = childText, !text.isEmpty { + elements.append(childLabel) + } + + if let errorText, showError, !errorText.isEmpty { + elements.append(errorLabel) + } + return elements + } + set { + super.accessibilityElements = newValue + } + } + /// Overriden to take the hit if there is an onClickSubscriber and the view is not a UIControl open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let labelPoint = convert(point, to: label) diff --git a/VDS/BaseClasses/View.swift b/VDS/BaseClasses/View.swift index a807c25c..9ba21d9c 100644 --- a/VDS/BaseClasses/View.swift +++ b/VDS/BaseClasses/View.swift @@ -46,6 +46,8 @@ open class View: UIView, ViewProtocol, UserInfoable { //-------------------------------------------------- open var shouldUpdateView: Bool = true + open var shouldUpdateAccessibility: Bool = true + /// Dictionary for keeping information for this Control use only Primitives. open var userInfo = [String: Primitive]() diff --git a/VDS/Components/Buttons/ButtonBase.swift b/VDS/Components/Buttons/ButtonBase.swift index b3b5f38d..a8443f58 100644 --- a/VDS/Components/Buttons/ButtonBase.swift +++ b/VDS/Components/Buttons/ButtonBase.swift @@ -51,6 +51,8 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable { /// Key of whether or not updateView() is called in setNeedsUpdate() open var shouldUpdateView: Bool = true + open var shouldUpdateAccessibility: Bool = true + open var surface: Surface = .light { didSet { setNeedsUpdate() } } /// Text that will be used in the titleLabel. diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index 9dd9bf1d..090215c2 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -108,6 +108,8 @@ open class Label: UILabel, ViewProtocol, UserInfoable { /// Key of whether or not updateView() is called in setNeedsUpdate() open var shouldUpdateView: Bool = true + open var shouldUpdateAccessibility: Bool = true + /// Will determine if a scaled font should be used for the font. open var useScaledFont: Bool = false { didSet { setNeedsUpdate() }} @@ -369,7 +371,7 @@ open class Label: UILabel, ViewProtocol, UserInfoable { } if let accessibilityElements, !accessibilityElements.isEmpty { - let staticText = UIAccessibilityElement(accessibilityContainer: self) + let staticText = AccessibilityActionElement(accessibilityContainer: self) staticText.accessibilityLabel = text staticText.accessibilityFrameInContainerSpace = bounds @@ -446,4 +448,9 @@ open class Label: UILabel, ViewProtocol, UserInfoable { accessibilityElements?.append(element) return element } + + public override func accessibilityActivate() -> Bool { + return false + } + } diff --git a/VDS/Components/RadioBox/RadioBoxItem.swift b/VDS/Components/RadioBox/RadioBoxItem.swift index 829d1a7d..8ca46b26 100644 --- a/VDS/Components/RadioBox/RadioBoxItem.swift +++ b/VDS/Components/RadioBox/RadioBoxItem.swift @@ -133,6 +133,30 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable { open var accessibilityValueText: String? + open var accessibilityLabelText: String { + var accessibilityLabels = [String]() + + accessibilityLabels.append("Radiobox") + + if let text { + accessibilityLabels.append(text) + } + + if let text = subText { + accessibilityLabels.append(text) + } + + if let text = subTextRight { + accessibilityLabels.append(text) + } + + if !isEnabled { + accessibilityLabels.append("dimmed") + } + + return accessibilityLabels.joined(separator: ", ") + } + //-------------------------------------------------- // MARK: - Configuration Properties //-------------------------------------------------- @@ -171,8 +195,10 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable { open override func setup() { super.setup() - isAccessibilityElement = true - accessibilityTraits = .button + isAccessibilityElement = false + selectorView.isAccessibilityElement = true + selectorView.accessibilityTraits = .button + addSubview(selectorView) selectorView.isUserInteractionEnabled = false @@ -242,12 +268,8 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable { /// Used to update any Accessibility properties. open override func updateAccessibility() { super.updateAccessibility() - setAccessibilityLabel(for: [textLabel, subTextLabel, subTextRightLabel]) - if let currentAccessibilityLabel = accessibilityLabel { - accessibilityLabel = "Radiobox, \(currentAccessibilityLabel)" - } else { - accessibilityLabel = "Radiobox" - } + accessibilityLabel = accessibilityLabelText + if let accessibilityValueText { accessibilityValue = strikethrough ? "\(strikethroughAccessibilityText), \(accessibilityValueText)" @@ -259,6 +281,35 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable { } } + open override var accessibilityElements: [Any]? { + get { + var items = [Any]() + items.append(selectorView) + + let elements = gatherAccessibilityElements(from: selectorView) + let views = elements.compactMap({ $0 as? UIView }) + + //update accessibilityLabel + selectorView.setAccessibilityLabel(for: views) + + //disabled + if !isEnabled { + if let label = selectorView.accessibilityLabel, !label.isEmpty { + selectorView.accessibilityLabel = "\(label), dimmed" + } else { + selectorView.accessibilityLabel = "dimmed" + } + } + + //append all children that are accessible + items.append(contentsOf: elements) + + return items + } + set {} + } + + //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- diff --git a/VDS/Components/TextFields/InputField/TextField.swift b/VDS/Components/TextFields/InputField/TextField.swift index e3d1d312..da109148 100644 --- a/VDS/Components/TextFields/InputField/TextField.swift +++ b/VDS/Components/TextFields/InputField/TextField.swift @@ -67,6 +67,8 @@ open class TextField: UITextField, ViewProtocol, Errorable { /// Key of whether or not updateView() is called in setNeedsUpdate() open var shouldUpdateView: Bool = true + open var shouldUpdateAccessibility: Bool = true + open var surface: Surface = .light { didSet { setNeedsUpdate() } } open var showError: Bool = false { didSet { setNeedsUpdate() } } diff --git a/VDS/Components/TextFields/TextArea/TextView.swift b/VDS/Components/TextFields/TextArea/TextView.swift index a0edb28c..6c9e92ab 100644 --- a/VDS/Components/TextFields/TextArea/TextView.swift +++ b/VDS/Components/TextFields/TextArea/TextView.swift @@ -48,6 +48,8 @@ open class TextView: UITextView, ViewProtocol, Errorable { /// Key of whether or not updateView() is called in setNeedsUpdate() open var shouldUpdateView: Bool = true + open var shouldUpdateAccessibility: Bool = true + open var surface: Surface = .light { didSet { setNeedsUpdate() } } /// Array of LabelAttributeModel objects used in rendering the text. diff --git a/VDS/Protocols/ViewProtocol.swift b/VDS/Protocols/ViewProtocol.swift index da730372..57b93982 100644 --- a/VDS/Protocols/ViewProtocol.swift +++ b/VDS/Protocols/ViewProtocol.swift @@ -16,6 +16,9 @@ public protocol ViewProtocol: AnyObject, Initable, Resettable, Enabling, Surface /// Key of whether or not updateView() is called in setNeedsUpdate() var shouldUpdateView: Bool { get set } + /// Key of whether or not updateAccessibility() is called in setNeedsUpdate() + var shouldUpdateAccessibility: Bool { get set } + /// Executed on initialization for this View. func initialSetup() @@ -30,12 +33,15 @@ public protocol ViewProtocol: AnyObject, Initable, Resettable, Enabling, Surface } extension ViewProtocol { + /// Called when there are changes in a View based off a change events or from local properties. public func setNeedsUpdate() { if shouldUpdateView { shouldUpdateView = false updateView() - updateAccessibility() + if shouldUpdateAccessibility { + updateAccessibility() + } shouldUpdateView = true } }