From a043bd33f2b933d50c8aba334d2c1f4c5f81e0cf Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 5 Jun 2024 16:15:35 -0500 Subject: [PATCH 1/8] added shouldUpdateAccessibility Signed-off-by: Matt Bruce --- VDS/BaseClasses/Control.swift | 2 ++ VDS/BaseClasses/View.swift | 2 ++ VDS/Components/Label/Label.swift | 2 ++ VDS/Components/TextFields/InputField/TextField.swift | 2 ++ VDS/Components/TextFields/TextArea/TextView.swift | 2 ++ VDS/Protocols/ViewProtocol.swift | 8 +++++++- 6 files changed, 17 insertions(+), 1 deletion(-) 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/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/Label/Label.swift b/VDS/Components/Label/Label.swift index 9e4dd18e..41a79c5e 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -110,6 +110,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() }} diff --git a/VDS/Components/TextFields/InputField/TextField.swift b/VDS/Components/TextFields/InputField/TextField.swift index b054741d..e7c32464 100644 --- a/VDS/Components/TextFields/InputField/TextField.swift +++ b/VDS/Components/TextFields/InputField/TextField.swift @@ -56,6 +56,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 f0b64f46..2fe19cb5 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 } } From b3f810500baffd4f48e652f916d9b60a1719c1a5 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 5 Jun 2024 16:15:53 -0500 Subject: [PATCH 2/8] split up accessibility Signed-off-by: Matt Bruce --- VDS/BaseClasses/Selector/SelectorBase.swift | 1 + .../Selector/SelectorItemBase.swift | 36 ++++++++-- VDS/Components/Buttons/ButtonBase.swift | 2 + VDS/Components/DatePicker/DatePicker.swift | 2 - VDS/Components/RadioBox/RadioBoxItem.swift | 67 ++++++++++++++++--- 5 files changed, 94 insertions(+), 14 deletions(-) diff --git a/VDS/BaseClasses/Selector/SelectorBase.swift b/VDS/BaseClasses/Selector/SelectorBase.swift index 0b29f4e2..809cb4aa 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 66772ce0..46d54fb5 100644 --- a/VDS/BaseClasses/Selector/SelectorItemBase.swift +++ b/VDS/BaseClasses/Selector/SelectorItemBase.swift @@ -147,6 +147,30 @@ open class SelectorItemBase: Control, Errorable, open var accessibilityValueText: String? + open var accessibilityLabelText: String { + var accessibilityLabels = [String]() + + accessibilityLabels.append("\(Selector.self)") + + if let text = labelText { + accessibilityLabels.append(text) + } + + if let text = childText { + accessibilityLabels.append(text) + } + + if !isEnabled { + accessibilityLabels.append("dimmed") + } + + if let errorText, showError { + accessibilityLabels.append("error, \(errorText)") + } + + return accessibilityLabels.joined(separator: ", ") + } + //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- @@ -162,9 +186,11 @@ open class SelectorItemBase: Control, Errorable, open override func setup() { super.setup() - selectorView.isAccessibilityElement = false - isAccessibilityElement = true - accessibilityTraits = .button + selectorView.isAccessibilityElement = true + selectorView.shouldUpdateAccessibility = false + + isAccessibilityElement = false + accessibilityElements = [selectorView, label, childLabel, errorLabel] addSubview(mainStackView) mainStackView.isUserInteractionEnabled = false @@ -195,8 +221,10 @@ 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 + } /// Resets to default settings. diff --git a/VDS/Components/Buttons/ButtonBase.swift b/VDS/Components/Buttons/ButtonBase.swift index 80010ee5..9dd60781 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/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index 7645cce5..c514001a 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -102,8 +102,6 @@ open class DatePicker: EntryFieldBase, DatePickerViewControllerDelegate, UIPopov super.setup() fieldStackView.isAccessibilityElement = true - fieldStackView.accessibilityLabel = "Date Picker" - fieldStackView.accessibilityHint = "Double Tap to open" // setting color config selectedDateLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() diff --git a/VDS/Components/RadioBox/RadioBoxItem.swift b/VDS/Components/RadioBox/RadioBoxItem.swift index 15f7c55a..b555a9b6 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 //-------------------------------------------------- From 225c44a84a1aa6e2568f10ad4a4690aede979e05 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 19 Jun 2024 09:35:51 -0500 Subject: [PATCH 3/8] added action element Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 4 ++++ VDS/Classes/AccessibilityActionElement.swift | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 VDS/Classes/AccessibilityActionElement.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index c17e971e..b44980ae 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -172,6 +172,7 @@ EAF193432C134F3800C68D18 /* TableCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 443DBAF92BDA303F0021497E /* TableCellItem.swift */; }; EAF1FE9929D4850E00101452 /* Clickable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF1FE9829D4850E00101452 /* Clickable.swift */; }; EAF1FE9B29DB1A6000101452 /* Changeable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF1FE9A29DB1A6000101452 /* Changeable.swift */; }; + EAF2F4762C231EAA007BFEDC /* AccessibilityActionElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF2F4752C231EAA007BFEDC /* AccessibilityActionElement.swift */; }; EAF7F0952899861000B287F5 /* CheckboxItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0932899861000B287F5 /* CheckboxItem.swift */; }; EAF7F09A2899B17200B287F5 /* CATransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0992899B17200B287F5 /* CATransaction.swift */; }; EAF7F0A0289AB7EC00B287F5 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F09F289AB7EC00B287F5 /* View.swift */; }; @@ -398,6 +399,7 @@ EAEEECAE2B1FC2BA00531FC2 /* ToggleViewChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ToggleViewChangeLog.txt; sourceTree = ""; }; EAF1FE9829D4850E00101452 /* Clickable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clickable.swift; sourceTree = ""; }; EAF1FE9A29DB1A6000101452 /* Changeable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Changeable.swift; sourceTree = ""; }; + EAF2F4752C231EAA007BFEDC /* AccessibilityActionElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityActionElement.swift; sourceTree = ""; }; EAF7F0932899861000B287F5 /* CheckboxItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckboxItem.swift; sourceTree = ""; }; EAF7F0992899B17200B287F5 /* CATransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATransaction.swift; sourceTree = ""; }; EAF7F09F289AB7EC00B287F5 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; }; @@ -743,6 +745,7 @@ EA985C1C296CD13600F2FF2E /* BundleManager.swift */, EAF7F0B8289C139800B287F5 /* ColorConfiguration.swift */, EAB5FEF02927F4AA00998C17 /* SelfSizingCollectionView.swift */, + EAF2F4752C231EAA007BFEDC /* AccessibilityActionElement.swift */, ); path = Classes; sourceTree = ""; @@ -1244,6 +1247,7 @@ EA0D1C412A6AD61C00E5C127 /* Typography+Additional.swift in Sources */, EAC925842911C63100091998 /* Colorable.swift in Sources */, 18B463A42BBD3C46005C4528 /* DropdownOptionModel.swift in Sources */, + EAF2F4762C231EAA007BFEDC /* AccessibilityActionElement.swift in Sources */, EAC58BFD2BE935C300BA39FA /* TitleLockupTextColor.swift in Sources */, EAACB89A2B927108006A3869 /* Valuing.swift in Sources */, EAE785312BA0A438009428EA /* UIImage+Helper.swift in Sources */, diff --git a/VDS/Classes/AccessibilityActionElement.swift b/VDS/Classes/AccessibilityActionElement.swift new file mode 100644 index 00000000..df3b6ed9 --- /dev/null +++ b/VDS/Classes/AccessibilityActionElement.swift @@ -0,0 +1,20 @@ +// +// AccessibilityActionElement.swift +// VDS +// +// Created by Matt Bruce on 6/19/24. +// + +import Foundation +import UIKit + +/// Custom UIAccessibilityElement that allows you to set the default action used in accessibilityActivate. +public class AccessibilityActionElement: UIAccessibilityElement { + var action: (() -> Void)? + + public override func accessibilityActivate() -> Bool { + action?() + return true + } +} + From 6d42ec599cd0f834b6ff993ca8cc12798d494f0d Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 19 Jun 2024 09:36:11 -0500 Subject: [PATCH 4/8] updated accessibility elements Signed-off-by: Matt Bruce --- .../Selector/SelectorItemBase.swift | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/VDS/BaseClasses/Selector/SelectorItemBase.swift b/VDS/BaseClasses/Selector/SelectorItemBase.swift index cee8d9c2..24e949e7 100644 --- a/VDS/BaseClasses/Selector/SelectorItemBase.swift +++ b/VDS/BaseClasses/Selector/SelectorItemBase.swift @@ -149,14 +149,13 @@ open class SelectorItemBase: Control, Errorable, open var accessibilityLabelText: String { var accessibilityLabels = [String]() - accessibilityLabels.append("\(Selector.self)") - if let text = labelText { + if let text = labelText, !text.isEmpty { accessibilityLabels.append(text) } - if let text = childText { + if let text = childText, !text.isEmpty { accessibilityLabels.append(text) } @@ -164,7 +163,7 @@ open class SelectorItemBase: Control, Errorable, accessibilityLabels.append("dimmed") } - if let errorText, showError { + if let errorText, showError, !errorText.isEmpty { accessibilityLabels.append("error, \(errorText)") } @@ -177,9 +176,7 @@ open class SelectorItemBase: Control, Errorable, /// 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. @@ -190,7 +187,6 @@ open class SelectorItemBase: Control, Errorable, selectorView.shouldUpdateAccessibility = false isAccessibilityElement = false - accessibilityElements = [selectorView, label, childLabel, errorLabel] addSubview(mainStackView) mainStackView.isUserInteractionEnabled = false @@ -211,6 +207,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 @@ -224,7 +221,30 @@ open class SelectorItemBase: Control, Errorable, 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 + } } /// Resets to default settings. From 4b06ef2e31d0dda57520a45967b7e65e118cc50d Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 19 Jun 2024 09:38:34 -0500 Subject: [PATCH 5/8] updated to use new accessibility Signed-off-by: Matt Bruce --- VDS/Components/Label/Label.swift | 62 +++++++++----------------------- 1 file changed, 16 insertions(+), 46 deletions(-) diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index 8c995ca1..c95e27a8 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -91,16 +91,14 @@ open class Label: UILabel, ViewProtocol, UserInfoable { private struct LabelAction { var range: NSRange var action: PassthroughSubject - var accessibilityId: Int = 0 - + var frame: CGRect = .zero func performAction() { action.send() } - init(range: NSRange, action: PassthroughSubject, accessibilityID: Int = 0) { + init(range: NSRange, action: PassthroughSubject) { self.range = range self.action = action - self.accessibilityId = accessibilityID } } @@ -265,24 +263,7 @@ open class Label: UILabel, ViewProtocol, UserInfoable { super.layoutSubviews() applyActions() } - - /// Addig custom accessibillty actions from the collection of attributes. - open override func accessibilityActivate() -> Bool { - guard let accessibleActions = accessibilityCustomActions else { return false } - - for actionable in actions { - for action in accessibleActions { - if action.hash == actionable.accessibilityId { - actionable.performAction() - return true - } - } - } - - return false - } - //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- @@ -375,10 +356,17 @@ open class Label: UILabel, ViewProtocol, UserInfoable { //see if the attribute is Actionable if let actionable = attribute as? any ActionLabelAttributeModel, mutableAttributedString.isValid(range: actionable.range) { //create a accessibleAction - let customAccessibilityAction = customAccessibilityAction(text: mutableAttributedString.string, range: actionable.range, accessibleText: actionable.accessibleText) - + let customAccessibilityAction = customAccessibilityElement(text: mutableAttributedString.string, + range: actionable.range, + accessibleText: actionable.accessibleText) + + // creat the action + let labelAction = LabelAction(range: actionable.range, action: actionable.action) + + customAccessibilityAction?.action = labelAction.performAction //create a wrapper for the attributes range, block and - actions.append(LabelAction(range: actionable.range, action: actionable.action, accessibilityID: customAccessibilityAction?.hashValue ?? -1)) + actions.append(labelAction) + isUserInteractionEnabled = true } } @@ -404,7 +392,7 @@ open class Label: UILabel, ViewProtocol, UserInfoable { } } - private func customAccessibilityAction(text: String?, range: NSRange, accessibleText: String? = nil) -> UIAccessibilityCustomAction? { + private func customAccessibilityElement(text: String?, range: NSRange, accessibleText: String? = nil) -> AccessibilityActionElement? { guard let text = text, let attributedText else { return nil } @@ -425,31 +413,13 @@ open class Label: UILabel, ViewProtocol, UserInfoable { let substringBounds = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer) // Create custom accessibility element - let element = UIAccessibilityElement(accessibilityContainer: self) + let element = AccessibilityActionElement(accessibilityContainer: self) element.accessibilityLabel = actionText element.accessibilityTraits = .link + element.accessibilityHint = "Double tap to open" element.accessibilityFrameInContainerSpace = substringBounds - - //TODO: accessibilityHint for Label -// element.accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "swipe_to_select_with_action_hint") - accessibilityElements = (accessibilityElements ?? []).compactMap{$0 as? UIAccessibilityElement}.filter { $0.accessibilityLabel != actionText } accessibilityElements?.append(element) - - let accessibleAction = UIAccessibilityCustomAction(name: actionText, target: self, selector: #selector(accessibilityCustomAction(_:))) - accessibilityCustomActions?.append(accessibleAction) - return accessibleAction - } - - @objc private func accessibilityCustomAction(_ action: UIAccessibilityCustomAction) { - - for actionable in actions { - if action.hash == actionable.accessibilityId { - actionable.performAction() - return - } - } + return element } } - - From 4aa081f6deb2d7741445e745b7c4fc1ba1d1de92 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 19 Jun 2024 09:49:19 -0500 Subject: [PATCH 6/8] updated for checkbox only Signed-off-by: Matt Bruce --- VDS/BaseClasses/Selector/SelectorItemBase.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/VDS/BaseClasses/Selector/SelectorItemBase.swift b/VDS/BaseClasses/Selector/SelectorItemBase.swift index 24e949e7..3de2b9db 100644 --- a/VDS/BaseClasses/Selector/SelectorItemBase.swift +++ b/VDS/BaseClasses/Selector/SelectorItemBase.swift @@ -176,7 +176,9 @@ open class SelectorItemBase: Control, Errorable, /// Executed on initialization for this View. open override func initialSetup() { super.initialSetup() - + selectorView.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. @@ -188,7 +190,6 @@ open class SelectorItemBase: Control, Errorable, isAccessibilityElement = false addSubview(mainStackView) - mainStackView.isUserInteractionEnabled = false mainStackView.addArrangedSubview(selectorStackView) mainStackView.addArrangedSubview(errorLabel) From c20027dd2c6aadae02d626942f6fe099fc4caf18 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 19 Jun 2024 10:53:19 -0500 Subject: [PATCH 7/8] updated for label click Signed-off-by: Matt Bruce --- .../Selector/SelectorItemBase.swift | 16 ++++++++- VDS/Components/Label/Label.swift | 35 +++++++++++++++++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/VDS/BaseClasses/Selector/SelectorItemBase.swift b/VDS/BaseClasses/Selector/SelectorItemBase.swift index 3de2b9db..bc92eb07 100644 --- a/VDS/BaseClasses/Selector/SelectorItemBase.swift +++ b/VDS/BaseClasses/Selector/SelectorItemBase.swift @@ -176,7 +176,7 @@ open class SelectorItemBase: Control, Errorable, /// Executed on initialization for this View. open override func initialSetup() { super.initialSetup() - selectorView.onClick = { [weak self] control in + onClick = { [weak self] control in self?.toggle() } } @@ -247,7 +247,21 @@ open class SelectorItemBase: Control, Errorable, 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) + let childLabelPoint = convert(point, to: childLabel) + if label.isAction(for: labelPoint) { + return label + } else if childLabel.isAction(for: childLabelPoint) { + return childLabel + } else { + return super.hitTest(point, with: event) + } + } + /// Resets to default settings. open override func reset() { super.reset() diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index c95e27a8..090215c2 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -371,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 @@ -385,12 +385,38 @@ open class Label: UILabel, ViewProtocol, UserInfoable { @objc private func textLinkTapped(_ gesture: UITapGestureRecognizer) { for actionable in actions { // This determines if we tapped on the desired range of text. - if gesture.didTapActionInLabel(self, inRange: actionable.range) { + let location = gesture.location(in: self) + if didTapActionInLabel(location, inRange: actionable.range) { actionable.performAction() return } } } + + public func isAction(for location: CGPoint) -> Bool { + for actionable in actions { + if didTapActionInLabel(location, inRange: actionable.range) { + return true + } + } + return false + } + + private func didTapActionInLabel(_ location: CGPoint, inRange targetRange: NSRange) -> Bool { + + guard let attributedText else { return false } + let layoutManager = NSLayoutManager() + let textContainer = NSTextContainer(size: bounds.size) + let textStorage = NSTextStorage(attributedString: attributedText) + layoutManager.addTextContainer(textContainer) + textStorage.addLayoutManager(layoutManager) + + let characterIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) + + guard let _ = attributedText.attribute(NSAttributedString.Key.action, at: characterIndex, effectiveRange: nil) as? String, characterIndex < attributedText.length else { return false } + return true + } + private func customAccessibilityElement(text: String?, range: NSRange, accessibleText: String? = nil) -> AccessibilityActionElement? { @@ -422,4 +448,9 @@ open class Label: UILabel, ViewProtocol, UserInfoable { accessibilityElements?.append(element) return element } + + public override func accessibilityActivate() -> Bool { + return false + } + } From 8d2125a91cafbd5f7634188b64c5056fb4985a01 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 19 Jun 2024 11:57:49 -0500 Subject: [PATCH 8/8] updated mainStackView Signed-off-by: Matt Bruce --- VDS/BaseClasses/Selector/SelectorItemBase.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/VDS/BaseClasses/Selector/SelectorItemBase.swift b/VDS/BaseClasses/Selector/SelectorItemBase.swift index bc92eb07..555102a2 100644 --- a/VDS/BaseClasses/Selector/SelectorItemBase.swift +++ b/VDS/BaseClasses/Selector/SelectorItemBase.swift @@ -191,6 +191,7 @@ open class SelectorItemBase: Control, Errorable, isAccessibilityElement = false addSubview(mainStackView) + mainStackView.isUserInteractionEnabled = false mainStackView.addArrangedSubview(selectorStackView) mainStackView.addArrangedSubview(errorLabel) selectorStackView.addArrangedSubview(selectorView)