From 73f27d1e8ba94d225f11396aa07c845bb137149d Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 20 Jun 2024 12:37:19 -0500 Subject: [PATCH] more code refactoring Signed-off-by: Matt Bruce --- VDS/BaseClasses/Control.swift | 2 - VDS/BaseClasses/Selector/SelectorBase.swift | 17 ++-- .../Selector/SelectorItemBase.swift | 77 ++++++++-------- VDS/BaseClasses/View.swift | 4 +- VDS/Classes/AccessibilityActionElement.swift | 3 +- VDS/Components/Badge/Badge.swift | 11 +-- .../BadgeIndicator/BadgeIndicator.swift | 21 ++--- .../Breadcrumbs/BreadcrumbItem.swift | 15 +-- VDS/Components/Buttons/ButtonBase.swift | 2 - .../Buttons/TextLink/TextLink.swift | 6 ++ .../Buttons/TextLinkCaret/TextLinkCaret.swift | 6 ++ VDS/Components/Checkbox/CheckboxGroup.swift | 3 +- .../Icon/ButtonIcon/ButtonIcon.swift | 72 ++++++++------- VDS/Components/Icon/Icon.swift | 13 +-- VDS/Components/Label/Label.swift | 10 +- .../Notification/Notification.swift | 12 +-- VDS/Components/Pagination/Pagination.swift | 30 ++++-- VDS/Components/RadioBox/RadioBoxGroup.swift | 8 +- VDS/Components/RadioBox/RadioBoxItem.swift | 90 +++++++----------- .../RadioButton/RadioButtonGroup.swift | 3 +- VDS/Components/Tabs/Tab.swift | 14 +-- VDS/Components/Tabs/Tabs.swift | 5 +- .../TextFields/EntryFieldBase.swift | 83 ++++++++--------- .../TextFields/InputField/TextField.swift | 2 - .../TextFields/TextArea/TextView.swift | 2 - .../TileContainer/TileContainer.swift | 12 +-- VDS/Components/TitleLockup/TitleLockup.swift | 27 +++--- VDS/Components/Toggle/Toggle.swift | 18 ++-- VDS/Components/Toggle/ToggleView.swift | 9 +- VDS/Components/Tooltip/Tooltip.swift | 34 +++---- VDS/Components/Tooltip/TooltipDialog.swift | 16 ++-- VDS/Protocols/AccessibilityUpdatable.swift | 87 ++++++++++++++++++ VDS/Protocols/Groupable.swift | 2 - VDS/Protocols/ViewProtocol.swift | 92 +------------------ 34 files changed, 399 insertions(+), 409 deletions(-) diff --git a/VDS/BaseClasses/Control.swift b/VDS/BaseClasses/Control.swift index aa7443a2..2a9fe769 100644 --- a/VDS/BaseClasses/Control.swift +++ b/VDS/BaseClasses/Control.swift @@ -127,8 +127,6 @@ open class Control: UIControl, ViewProtocol, UserInfoable, Clickable { //-------------------------------------------------- // MARK: - Accessibility //-------------------------------------------------- - open var shouldUpdateAccessibility: Bool = true - open var accessibilityAction: ((Control) -> Void)? private var _isAccessibilityElement: Bool = false diff --git a/VDS/BaseClasses/Selector/SelectorBase.swift b/VDS/BaseClasses/Selector/SelectorBase.swift index d1683aa3..fb8d771e 100644 --- a/VDS/BaseClasses/Selector/SelectorBase.swift +++ b/VDS/BaseClasses/Selector/SelectorBase.swift @@ -104,6 +104,16 @@ open class SelectorBase: Control, SelectorControlable { onClick = { control in control.toggle() } + + bridge_accessibilityLabelBlock = { [weak self] in + guard let self else { return "" } + return "\(Self.self)\(showError ? ", error" : "")" + } + + bridge_accessibilityHintBlock = { [weak self] in + guard let self else { return "" } + return !isEnabled ? "" : "Double tap to activate." + } } /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. @@ -119,13 +129,6 @@ open class SelectorBase: Control, SelectorControlable { setNeedsLayout() layoutIfNeeded() } - - /// Used to update any Accessibility properties.ß - 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. open func toggle() { } diff --git a/VDS/BaseClasses/Selector/SelectorItemBase.swift b/VDS/BaseClasses/Selector/SelectorItemBase.swift index 0326ac52..44ed01b1 100644 --- a/VDS/BaseClasses/Selector/SelectorItemBase.swift +++ b/VDS/BaseClasses/Selector/SelectorItemBase.swift @@ -145,8 +145,6 @@ open class SelectorItemBase: Control, Errorable, Changea open var hiddenValue: AnyHashable? { didSet { setNeedsUpdate() } } - open var accessibilityValueText: String? - open override var accessibilityAction: ((Control) -> Void)? { didSet { selectorView.accessibilityAction = { [weak self] selectorItemBase in @@ -156,34 +154,6 @@ open class SelectorItemBase: Control, Errorable, Changea } } - open var accessibilityLabelText: String { - var accessibilityLabels = [String]() - - if isSelected { - accessibilityLabels.append("selected") - } - - 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 //-------------------------------------------------- @@ -199,15 +169,48 @@ open class SelectorItemBase: Control, Errorable, Changea guard let self, isEnabled else { return } toggle() } + + selectorView.bridge_accessibilityLabelBlock = { [weak self ] in + guard let self else { return "" } + var accessibilityLabels = [String]() + + if isSelected { + accessibilityLabels.append("selected") + } + + 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: ", ") + } + + selectorView.bridge_accessibilityHintBlock = { [weak self] in + guard let self else { return "" } + return !isEnabled ? "" : "Double tap to activate." + } + } /// 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 = true - selectorView.shouldUpdateAccessibility = false - + selectorView.isAccessibilityElement = true isAccessibilityElement = false addSubview(mainStackView) @@ -235,14 +238,6 @@ open class SelectorItemBase: Control, Errorable, Changea selectorView.isEnabled = isEnabled selectorView.surface = surface } - - /// Used to update any Accessibility properties. - open override func updateAccessibility() { - super.updateAccessibility() - selectorView.accessibilityLabel = accessibilityLabelText - selectorView.accessibilityHint = !isEnabled ? "" : "Double tap to activate." - accessibilityValue = accessibilityValueText - } open override var accessibilityElements: [Any]? { get { diff --git a/VDS/BaseClasses/View.swift b/VDS/BaseClasses/View.swift index 288a8cdc..c7df1765 100644 --- a/VDS/BaseClasses/View.swift +++ b/VDS/BaseClasses/View.swift @@ -94,8 +94,6 @@ open class View: UIView, ViewProtocol, UserInfoable { //-------------------------------------------------- // MARK: - Accessibility //-------------------------------------------------- - open var shouldUpdateAccessibility: Bool = true - open var accessibilityAction: ((View) -> Void)? private var _isAccessibilityElement: Bool = false @@ -219,7 +217,7 @@ open class View: UIView, ViewProtocol, UserInfoable { return block() } else { - return true + return super.accessibilityActivate() } // } diff --git a/VDS/Classes/AccessibilityActionElement.swift b/VDS/Classes/AccessibilityActionElement.swift index cc3487f9..7717839c 100644 --- a/VDS/Classes/AccessibilityActionElement.swift +++ b/VDS/Classes/AccessibilityActionElement.swift @@ -13,7 +13,8 @@ public class AccessibilityActionElement: UIAccessibilityElement { public var accessibilityAction: AXVoidReturnBlock? public override func accessibilityActivate() -> Bool { - accessibilityAction?() + guard let accessibilityAction else { return super.accessibilityActivate() } + accessibilityAction() return true } } diff --git a/VDS/Components/Badge/Badge.swift b/VDS/Components/Badge/Badge.swift index 43f702fa..5753428e 100644 --- a/VDS/Components/Badge/Badge.swift +++ b/VDS/Components/Badge/Badge.swift @@ -147,6 +147,11 @@ open class Badge: View { label.widthGreaterThanEqualTo(constant: minWidth) maxWidthConstraint = label.widthLessThanEqualTo(constant: 0).with { $0.isActive = false } clipsToBounds = true + + bridge_accessibilityLabelBlock = { [weak self] in + guard let self else { return "" } + return text + } } /// Resets to default settings. @@ -179,10 +184,4 @@ open class Badge: View { label.surface = surface label.isEnabled = isEnabled } - - open override func updateAccessibility() { - super.updateAccessibility() - - accessibilityLabel = text - } } diff --git a/VDS/Components/BadgeIndicator/BadgeIndicator.swift b/VDS/Components/BadgeIndicator/BadgeIndicator.swift index 740538d1..60025390 100644 --- a/VDS/Components/BadgeIndicator/BadgeIndicator.swift +++ b/VDS/Components/BadgeIndicator/BadgeIndicator.swift @@ -292,6 +292,16 @@ open class BadgeIndicator: View { label.centerYAnchor.constraint(equalTo: badgeView.centerYAnchor).isActive = true labelContraints.isActive = true + bridge_accessibilityLabelBlock = { [weak self] in + guard let self else { return "" } + if let accessibilityText { + return kind == .numbered ? label.text + " " + accessibilityText : accessibilityText + } else if kind == .numbered { + return label.text + } else { + return "Simple" + } + } } /// Resets to default settings. @@ -347,17 +357,6 @@ open class BadgeIndicator: View { setNeedsLayout() } - open override func updateAccessibility() { - super.updateAccessibility() - if let accessibilityText { - accessibilityLabel = kind == .numbered ? label.text + " " + accessibilityText : accessibilityText - } else if kind == .numbered { - accessibilityLabel = label.text - } else { - accessibilityLabel = "Simple" - } - } - open override func layoutSubviews() { super.layoutSubviews() diff --git a/VDS/Components/Breadcrumbs/BreadcrumbItem.swift b/VDS/Components/Breadcrumbs/BreadcrumbItem.swift index 08e58e60..bf6d4ed1 100644 --- a/VDS/Components/Breadcrumbs/BreadcrumbItem.swift +++ b/VDS/Components/Breadcrumbs/BreadcrumbItem.swift @@ -82,6 +82,15 @@ open class BreadcrumbItem: ButtonBase { isAccessibilityElement = true accessibilityTraits = .link + bridge_accessibilityHintBlock = { [weak self] in + guard let self else { return "" } + return !isEnabled ? "" : "Double tap to open." + } + + bridge_accessibilityLabelBlock = { [weak self] in + guard let self else { return "" } + return text + } } /// Used to make changes to the View based off a change events or from local properties. @@ -134,10 +143,4 @@ open class BreadcrumbItem: ButtonBase { setNeedsUpdate() } - /// Used to update any Accessibility properties. - open override func updateAccessibility() { - super.updateAccessibility() - accessibilityLabel = text - } - } diff --git a/VDS/Components/Buttons/ButtonBase.swift b/VDS/Components/Buttons/ButtonBase.swift index e5a80e2e..d7b80c8a 100644 --- a/VDS/Components/Buttons/ButtonBase.swift +++ b/VDS/Components/Buttons/ButtonBase.swift @@ -175,8 +175,6 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable { //-------------------------------------------------- // MARK: - Accessibility //-------------------------------------------------- - open var shouldUpdateAccessibility: Bool = true - open var accessibilityAction: ((ButtonBase) -> Void)? private var _isAccessibilityElement: Bool = false diff --git a/VDS/Components/Buttons/TextLink/TextLink.swift b/VDS/Components/Buttons/TextLink/TextLink.swift index e0aac99c..f77dfa79 100644 --- a/VDS/Components/Buttons/TextLink/TextLink.swift +++ b/VDS/Components/Buttons/TextLink/TextLink.swift @@ -105,6 +105,12 @@ open class TextLink: ButtonBase { lineHeightConstraint = line.height(constant: 1) lineHeightConstraint?.isActive = true } + + bridge_accessibilityHintBlock = { [weak self] in + guard let self else { return "" } + return !isEnabled ? "" : "Double tap to open." + } + } /// Used to make changes to the View based off a change events or from local properties. diff --git a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift index 83d057c1..d1522d02 100644 --- a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift +++ b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift @@ -86,6 +86,12 @@ open class TextLinkCaret: ButtonBase { accessibilityTraits = .link titleLabel?.numberOfLines = 0 titleLabel?.lineBreakMode = .byWordWrapping + + bridge_accessibilityHintBlock = { [weak self] in + guard let self else { return "" } + return !isEnabled ? "" : "Double tap to open." + } + } /// Used to make changes to the View based off a change events or from local properties. diff --git a/VDS/Components/Checkbox/CheckboxGroup.swift b/VDS/Components/Checkbox/CheckboxGroup.swift index 242e193e..b450d4d5 100644 --- a/VDS/Components/Checkbox/CheckboxGroup.swift +++ b/VDS/Components/Checkbox/CheckboxGroup.swift @@ -47,8 +47,6 @@ open class CheckboxGroup: SelectorGroupBase, SelectorGroupMultiSel $0.surface = model.surface $0.inputId = model.inputId $0.hiddenValue = model.value - $0.accessibilityLabel = model.accessibileText - $0.accessibilityValueText = "item \(index+1) of \(selectorModels.count)" $0.labelText = model.labelText $0.labelTextAttributes = model.labelTextAttributes $0.childText = model.childText @@ -56,6 +54,7 @@ open class CheckboxGroup: SelectorGroupBase, SelectorGroupMultiSel $0.isSelected = model.selected $0.errorText = model.errorText $0.showError = model.showError + $0.selectorView.bridge_accessibilityValueBlock = { "item \(index+1) of \(selectorModels.count)" } } } } diff --git a/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift b/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift index 64ce0da5..bf8a50f7 100644 --- a/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift +++ b/VDS/Components/Icon/ButtonIcon/ButtonIcon.swift @@ -30,7 +30,7 @@ open class ButtonIcon: Control, Changeable { public required init?(coder: NSCoder) { super.init(coder: coder) } - + //-------------------------------------------------- // MARK: - Enums //-------------------------------------------------- @@ -43,7 +43,7 @@ open class ButtonIcon: Control, Changeable { public enum SurfaceType: String, CaseIterable { case colorFill, media } - + /// Enum used to describe the size of button icon. public enum Size: String, EnumSubset { case large @@ -105,12 +105,12 @@ open class ButtonIcon: Control, Changeable { return .init(x: 6, y: 6) } } - + //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- public var onChangeSubscriber: AnyCancellable? - + ///Badge Indicator object used to render for the ButtonIcon. open var badgeIndicator = BadgeIndicator().with { $0.translatesAutoresizingMaskIntoConstraints = false @@ -140,10 +140,10 @@ open class ButtonIcon: Control, Changeable { open var selectedIconName: Icon.Name? { didSet { setNeedsUpdate() } } open var selectedIconColorConfiguration: SurfaceColorConfiguration? { didSet { setNeedsUpdate() } } - + /// Sets the size of button icon and icon. open var size: Size = .large { didSet { setNeedsUpdate() } } - + /// If provided, the button icon will have a box shadow. open var floating: Bool = false { didSet { setNeedsUpdate() } } @@ -152,7 +152,7 @@ open class ButtonIcon: Control, Changeable { /// If set to true, the button icon will not have a border. open var hideBorder: Bool = true { didSet { setNeedsUpdate() } } - + /// If provided, the badge indicator will present. open var showBadgeIndicator: Bool = false { didSet { setNeedsUpdate() } } @@ -169,14 +169,14 @@ open class ButtonIcon: Control, Changeable { /// Used to move the icon inside the button in both x and y axis. open var iconOffset: CGPoint = .init(x: 0, y: 0) { didSet { setNeedsUpdate() } } - + /// Sets a custom size of button icon container. open var customContainerSize: Int? { didSet { setNeedsUpdate() } } - + /// Sets a custom size of the icon. open var customIconSize: Int? { didSet { setNeedsUpdate() } } - + /// Sets a custom badgeIndicator offset open var customBadgeIndicatorOffset: CGPoint? { didSet { setNeedsUpdate() } } @@ -246,7 +246,7 @@ open class ButtonIcon: Control, Changeable { SurfaceColorConfiguration(.clear, .clear).eraseToAnyColorable() }() } - + private struct LowContrastColorFillConfiguration: Configuration { var kind: Kind = .lowContrast var surfaceType: SurfaceType = .colorFill @@ -255,7 +255,7 @@ open class ButtonIcon: Control, Changeable { SurfaceColorConfiguration(VDSColor.paletteGray44.withAlphaComponent(0.06), VDSColor.paletteGray44.withAlphaComponent(0.26)).eraseToAnyColorable() }() } - + private struct LowContrastColorFillFloatingConfiguration: Configuration, DropShadowableConfiguration { var kind: Kind = .lowContrast var surfaceType: SurfaceType = .colorFill @@ -277,7 +277,7 @@ open class ButtonIcon: Control, Changeable { } var configurations: [DropShadowable] { [dropshadow1Configuration, dropshadow2Configuration] } } - + private struct LowContrastMediaConfiguration: Configuration, Borderable { var kind: Kind = .lowContrast var surfaceType: SurfaceType = .media @@ -290,7 +290,7 @@ open class ButtonIcon: Control, Changeable { SurfaceColorConfiguration(VDSColor.elementsLowcontrastOnlight, VDSColor.elementsLowcontrastOndark).eraseToAnyColorable() }() } - + private struct LowContrastMediaFloatingConfiguration: Configuration, DropShadowableConfiguration { var kind: Kind = .lowContrast var surfaceType: SurfaceType = .media @@ -325,10 +325,10 @@ open class ButtonIcon: Control, Changeable { $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: [.selected, .disabled]) }.eraseToAnyColorable() - + }() } - + private struct HighContrastFloatingConfiguration: Configuration, DropShadowableConfiguration { var kind: Kind = .highContrast var surfaceType: SurfaceType = .colorFill @@ -357,9 +357,9 @@ open class ButtonIcon: Control, Changeable { } var configurations: [DropShadowable] { [dropshadow1Configuration, dropshadow2Configuration] } } - + private var badgeIndicatorDefaultSize: CGSize = .zero - + //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- @@ -367,11 +367,11 @@ open class ButtonIcon: Control, Changeable { open override func setup() { super.setup() isAccessibilityElement = false - + //create a layoutGuide for the icon to key off of let iconLayoutGuide = UILayoutGuide() addLayoutGuide(iconLayoutGuide) - + //add the icon addSubview(icon) @@ -379,7 +379,7 @@ open class ButtonIcon: Control, Changeable { addSubview(badgeIndicator) badgeIndicator.isHidden = !showBadgeIndicator badgeIndicatorDefaultSize = badgeIndicator.frame.size - + //determines the height/width of the icon layoutGuideWidthConstraint = iconLayoutGuide.width(constant: size.containerSize) layoutGuideHeightConstraint = iconLayoutGuide.height(constant: size.containerSize) @@ -388,7 +388,7 @@ open class ButtonIcon: Control, Changeable { badgeIndicatorCenterXConstraint = badgeIndicator.centerXAnchor.constraint(equalTo: icon.centerXAnchor) badgeIndicatorCenterYConstraint = icon.centerYAnchor.constraint(equalTo: badgeIndicator.centerYAnchor) badgeIndicatorCenterYConstraint?.isActive = true - + badgeIndicatorLeadingConstraint?.isActive = true //pin layout guide iconLayoutGuide @@ -396,7 +396,7 @@ open class ButtonIcon: Control, Changeable { .pinLeading() .pinTrailing(0, .defaultHigh) .pinBottom(0, .defaultHigh) - + //determines the center point of the icon centerXConstraint = icon.centerXAnchor.constraint(equalTo: iconLayoutGuide.centerXAnchor, constant: 0) centerXConstraint?.activate() @@ -414,14 +414,14 @@ open class ButtonIcon: Control, Changeable { } } } - + /// This will change the state of the Selector and execute the actionBlock if provided. open func toggle() { //removed error isSelected.toggle() sendActions(for: .valueChanged) } - + /// Resets to default settings. open override func reset() { super.reset() @@ -437,7 +437,7 @@ open class ButtonIcon: Control, Changeable { showBadgeIndicator = false selectable = false badgeIndicatorModel = nil - onChange = nil + onChange = nil shouldUpdateView = true setNeedsUpdate() } @@ -464,16 +464,18 @@ open class ButtonIcon: Control, Changeable { setNeedsLayout() } - open override func updateAccessibility() { - super.updateAccessibility() - var elements = [Any]() - if iconName != nil { - elements.append(icon) + open override var accessibilityElements: [Any]? { + get { + var elements = [Any]() + if iconName != nil { + elements.append(icon) + } + if badgeIndicatorModel != nil && showBadgeIndicator { + elements.append(badgeIndicator) + } + return elements.count > 0 ? elements : nil } - if badgeIndicatorModel != nil && showBadgeIndicator { - elements.append(badgeIndicator) - } - accessibilityElements = elements.count > 0 ? elements : nil + set { } } open override func layoutSubviews() { diff --git a/VDS/Components/Icon/Icon.swift b/VDS/Components/Icon/Icon.swift index ac4a5818..2ac22ac8 100644 --- a/VDS/Components/Icon/Icon.swift +++ b/VDS/Components/Icon/Icon.swift @@ -94,6 +94,12 @@ open class Icon: View { isAccessibilityElement = true accessibilityTraits = .image + + bridge_accessibilityLabelBlock = { [weak self] in + guard let self else { return "" } + return name?.rawValue ?? "icon" + } + } /// Used to make changes to the View based off a change events or from local properties. @@ -118,12 +124,7 @@ open class Icon: View { super.reset() color = VDSColor.paletteBlack imageView.image = nil - } - - open override func updateAccessibility() { - super.updateAccessibility() - accessibilityLabel = name?.rawValue ?? "icon" - } + } } extension UIImage { diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index b0a85e7e..15ed4b45 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -213,7 +213,12 @@ open class Label: UILabel, ViewProtocol, UserInfoable { } } - open func setup() {} + open func setup() { + bridge_accessibilityLabelBlock = { [weak self] in + guard let self else { return "" } + return text + } + } open func reset() { shouldUpdateView = false @@ -240,7 +245,6 @@ open class Label: UILabel, ViewProtocol, UserInfoable { } open func updateAccessibility() { - accessibilityLabel = text if isEnabled { accessibilityTraits.remove(.notEnabled) } else { @@ -456,8 +460,6 @@ open class Label: UILabel, ViewProtocol, UserInfoable { //-------------------------------------------------- // MARK: - Accessibility //-------------------------------------------------- - open var shouldUpdateAccessibility: Bool = true - open var accessibilityAction: ((Label) -> Void)? private var _isAccessibilityElement: Bool = false diff --git a/VDS/Components/Notification/Notification.swift b/VDS/Components/Notification/Notification.swift index 5f180b71..66be33c4 100644 --- a/VDS/Components/Notification/Notification.swift +++ b/VDS/Components/Notification/Notification.swift @@ -265,6 +265,12 @@ open class Notification: View { isAccessibilityElement = false accessibilityElements = [closeButton, typeIcon, titleLabel, subTitleLabel, buttonGroup] closeButton.accessibilityTraits = [.button] + closeButton.accessibilityLabel = "Close Notification" + + typeIcon.bridge_accessibilityLabelBlock = { [weak self] in + guard let self else { return "" } + return style.accessibleText + } } /// Resets to default settings. @@ -372,12 +378,6 @@ 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 13478d85..8988aaa4 100644 --- a/VDS/Components/Pagination/Pagination.swift +++ b/VDS/Components/Pagination/Pagination.swift @@ -86,6 +86,10 @@ open class Pagination: View { } } + private var paginationDescription: String { + "Page \(selectedPage) of \(total) selected" + } + //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- @@ -148,14 +152,26 @@ open class Pagination: View { guard let self else { return } self.selectedPage = max(0, self.selectedPage - 1) } + + collectionContainerView.bridge_accessibilityLabelBlock = { [weak self] in + guard let self else { return "" } + return "Pagination containing \(total) pages" + } + + collectionContainerView.bridge_accessibilityValueBlock = { [weak self] in + guard let self else { return "" } + return paginationDescription + } } - ///Updating the accessiblity values i.e elements, label, value other items for the component. - open override func updateAccessibility() { - super.updateAccessibility() - accessibilityElements = [previousButton, collectionContainerView, nextButton] - collectionContainerView.accessibilityLabel = "Pagination containing \(total) pages" - collectionContainerView.accessibilityValue = "Page \(selectedPage) of \(total) selected" + open override var accessibilityElements: [Any]? { + get { + let views: [UIView] = [previousButton, collectionContainerView, nextButton] + return views.filter({ $0.isHidden == false }) + } + set { + + } } /// Used to make changes to the View based off a change events or from local properties. @@ -176,7 +192,7 @@ open class Pagination: View { updateSelection() 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") + UIAccessibility.post(notification: .announcement, argument: paginationDescription) } } diff --git a/VDS/Components/RadioBox/RadioBoxGroup.swift b/VDS/Components/RadioBox/RadioBoxGroup.swift index 296ea8ed..c58802c0 100644 --- a/VDS/Components/RadioBox/RadioBoxGroup.swift +++ b/VDS/Components/RadioBox/RadioBoxGroup.swift @@ -42,8 +42,6 @@ open class RadioBoxGroup: SelectorGroupBase, SelectorGroupSingleSe if let selectorModels { items = selectorModels.enumerated().map { index, model in return RadioBoxItem().with { - $0.accessibilityLabel = model.accessibileText - $0.accessibilityValue = "item \(index+1) of \(selectorModels.count)" $0.text = model.text $0.textAttributes = model.textAttributes $0.subText = model.subText @@ -56,7 +54,7 @@ open class RadioBoxGroup: SelectorGroupBase, SelectorGroupSingleSe $0.isSelected = model.selected $0.strikethrough = model.strikethrough $0.strikethroughAccessibilityText = model.strikethroughAccessibileText - $0.accessibilityValueText = "item \(index+1) of \(selectorModels.count)" + $0.selectorView.bridge_accessibilityValueBlock = { "item \(index+1) of \(selectorModels.count)" } } } } @@ -111,7 +109,7 @@ extension RadioBoxGroup { /// Current Surface and this is used to pass down to child objects that implement Surfacable public var surface: Surface public var inputId: String? - public var value: AnyHashable? + public var value: String? public var accessibileText: String? public var text: String /// Array of LabelAttributeModel objects used in rendering the text. @@ -126,7 +124,7 @@ extension RadioBoxGroup { public var strikethrough: Bool = false public var strikethroughAccessibileText: String - public init(disabled: Bool, surface: Surface = .light, inputId: String? = nil, value: AnyHashable? = nil, + public init(disabled: Bool, surface: Surface = .light, inputId: String? = nil, value: String? = nil, text: String = "", textAttributes: [any LabelAttributeModel]? = nil, subText: String? = nil, subTextAttributes: [any LabelAttributeModel]? = nil, subTextRight: String? = nil, subTextRightAttributes: [any LabelAttributeModel]? = nil, diff --git a/VDS/Components/RadioBox/RadioBoxItem.swift b/VDS/Components/RadioBox/RadioBoxItem.swift index 71b05494..562e1e54 100644 --- a/VDS/Components/RadioBox/RadioBoxItem.swift +++ b/VDS/Components/RadioBox/RadioBoxItem.swift @@ -74,7 +74,7 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable { } /// Selector for this RadioBox. - open var selectorView = View() + open var selectorView = View().with { $0.accessibilityIdentifier = "RadioBox" } /// If provided, the RadioBox text will be rendered. open var text: String? { didSet { setNeedsUpdate() } } @@ -125,12 +125,10 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable { open var inputId: String? { didSet { setNeedsUpdate() } } - open var value: AnyHashable? { hiddenValue } + open var value: String? { hiddenValue } - open var hiddenValue: AnyHashable? { didSet { setNeedsUpdate() } } + open var hiddenValue: String? { didSet { setNeedsUpdate() } } - open var accessibilityValueText: String? - open override var accessibilityAction: ((Control) -> Void)? { didSet { selectorView.accessibilityAction = { [weak self] selectorItemBase in @@ -140,34 +138,6 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable { } } - open var accessibilityLabelText: String { - var accessibilityLabels = [String]() - - if isSelected { - accessibilityLabels.append("selected") - } - - accessibilityLabels.append("Radiobox") - - if let text, !text.isEmpty { - accessibilityLabels.append(text) - } - - if let text = subText, !text.isEmpty { - accessibilityLabels.append(text) - } - - if let text = subTextRight, !text.isEmpty { - accessibilityLabels.append(text) - } - - if !isEnabled { - accessibilityLabels.append("dimmed") - } - - return accessibilityLabels.joined(separator: ", ") - } - //-------------------------------------------------- // MARK: - Configuration Properties //-------------------------------------------------- @@ -201,15 +171,39 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable { control.toggle() } - if #available(iOS 17.0, *) { - accessibilityHintBlock = { [weak self] in - - return "foo" + selectorView.bridge_accessibilityLabelBlock = { [weak self] in + guard let self else { return "" } + var accessibilityLabels = [String]() + + if isSelected { + accessibilityLabels.append("selected") } - } else { - // Fallback on earlier versions + + accessibilityLabels.append("Radiobox") + + if let text, !text.isEmpty { + accessibilityLabels.append(text) + } + + if let text = subText, !text.isEmpty { + accessibilityLabels.append(text) + } + + if let text = subTextRight, !text.isEmpty { + accessibilityLabels.append(text) + } + + if strikethrough { + accessibilityLabels.append(strikethroughAccessibilityText) + } + + if !isEnabled { + accessibilityLabels.append("dimmed") + } + + return accessibilityLabels.joined(separator: ", ") } - + } /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. @@ -287,22 +281,6 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable { setNeedsLayout() } - /// Used to update any Accessibility properties. - open override func updateAccessibility() { - super.updateAccessibility() - selectorView.accessibilityLabel = accessibilityLabelText - - if let accessibilityValueText { - selectorView.accessibilityValue = strikethrough - ? "\(strikethroughAccessibilityText), \(accessibilityValueText)" - : accessibilityValueText - } else { - selectorView.accessibilityValue = strikethrough - ? "\(strikethroughAccessibilityText)" - : accessibilityValueText - } - } - open override var accessibilityElements: [Any]? { get { var items = [Any]() diff --git a/VDS/Components/RadioButton/RadioButtonGroup.swift b/VDS/Components/RadioButton/RadioButtonGroup.swift index ca91f3e5..f44f5587 100644 --- a/VDS/Components/RadioButton/RadioButtonGroup.swift +++ b/VDS/Components/RadioButton/RadioButtonGroup.swift @@ -46,8 +46,6 @@ open class RadioButtonGroup: SelectorGroupBase, SelectorGroupSi $0.surface = model.surface $0.inputId = model.inputId $0.hiddenValue = model.value - $0.accessibilityLabel = model.accessibileText - $0.accessibilityValueText = "item \(index+1) of \(selectorModels.count)" $0.labelText = model.labelText $0.labelTextAttributes = model.labelTextAttributes $0.childText = model.childText @@ -55,6 +53,7 @@ open class RadioButtonGroup: SelectorGroupBase, SelectorGroupSi $0.isSelected = model.selected $0.errorText = model.errorText $0.showError = model.showError + $0.selectorView.bridge_accessibilityValueBlock = { "item \(index+1) of \(selectorModels.count)" } } } } diff --git a/VDS/Components/Tabs/Tab.swift b/VDS/Components/Tabs/Tab.swift index 3b22c8f5..4c0b55cd 100644 --- a/VDS/Components/Tabs/Tab.swift +++ b/VDS/Components/Tabs/Tab.swift @@ -88,8 +88,6 @@ extension Tabs { open var minWidth: CGFloat = 44.0 { didSet { setNeedsUpdate() } } open override var shouldHighlight: Bool { false } - - open var accessibilityValueText: String? //-------------------------------------------------- // MARK: - Configuration @@ -151,6 +149,11 @@ extension Tabs { labelTopConstraint = label.pinTop(anchor: layoutGuide.topAnchor) labelLeadingConstraint = label.pinLeading(anchor: layoutGuide.leadingAnchor) labelBottomConstraint = label.pinBottom(anchor: layoutGuide.bottomAnchor, priority: .defaultHigh) + + bridge_accessibilityLabelBlock = { [weak self] in + guard let self else { return "" } + return text + } } /// Used to make changes to the View based off a change events or from local properties. @@ -176,13 +179,6 @@ extension Tabs { setNeedsLayout() } - /// Used to update any Accessibility properties. - open override func updateAccessibility() { - super.updateAccessibility() - accessibilityLabel = text - accessibilityValue = accessibilityValueText - } - open override func layoutSubviews() { super.layoutSubviews() diff --git a/VDS/Components/Tabs/Tabs.swift b/VDS/Components/Tabs/Tabs.swift index 4c463900..88ae02e7 100644 --- a/VDS/Components/Tabs/Tabs.swift +++ b/VDS/Components/Tabs/Tabs.swift @@ -305,7 +305,10 @@ open class Tabs: View { tabItem.orientation = orientation tabItem.surface = surface tabItem.indicatorPosition = indicatorPosition - tabItem.accessibilityValueText = "\(index+1) of \(tabViews.count) Tabs" + tabItem.bridge_accessibilityValueBlock = { [weak self] in + guard let self else { return "" } + return "\(index+1) of \(tabViews.count) Tabs" + } } } diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index 4e7422ee..2ef89480 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -94,12 +94,9 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { /// This is the view that will be wrapped with the border for userInteraction. /// The only subview of this view is the fieldStackView - internal var containerView: UIView = { - return UIView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.isAccessibilityElement = true - } - }() + internal var containerView = View().with { + $0.isAccessibilityElement = true + } /// This is set by a local method. internal var bottomContainerView: UIView! @@ -244,27 +241,6 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { open var rules = [AnyRule]() - open var accessibilityLabelText: String { - var accessibilityLabels = [String]() - - if let text = titleLabel.text?.trimmingCharacters(in: .whitespaces) { - accessibilityLabels.append(text) - } - if isReadOnly { - accessibilityLabels.append("read only") - } - if !isEnabled { - accessibilityLabels.append("dimmed") - } - if let errorText, showError { - accessibilityLabels.append("error, \(errorText)") - } - - accessibilityLabels.append("\(Self.self)") - - return accessibilityLabels.joined(separator: ", ") - } - open var accessibilityHintText: String = "Double tap to open" //-------------------------------------------------- @@ -283,11 +259,11 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { .pinBottom() trailingEqualsConstraint = layoutGuide.pinTrailing(anchor: trailingAnchor) - + // width constraints trailingLessThanEqualsConstraint = layoutGuide.pinTrailingLessThanOrEqualTo(anchor: trailingAnchor)?.deactivate() widthConstraint = layoutGuide.widthAnchor.constraint(equalToConstant: 0).deactivate() - + // Add mainStackView to the view addSubview(mainStackView) @@ -301,7 +277,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { //InputContainer, Icons, Buttons containerView.addSubview(fieldStackView) fieldStackView.pinToSuperView(.uniform(VDSLayout.space3X)) - + let fieldContainerView = getFieldContainer() fieldContainerView.translatesAutoresizingMaskIntoConstraints = false @@ -309,11 +285,11 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { fieldStackView.addArrangedSubview(fieldContainerView) fieldStackView.addArrangedSubview(statusIcon) fieldStackView.setCustomSpacing(VDSLayout.space3X, after: fieldContainerView) - + //get the container this is what show helper text, error text //can include other for character count, max length bottomContainerView = getBottomContainer() - + //this is the vertical stack that contains error text, helper text bottomContainerStackView.addArrangedSubview(errorLabel) bottomContainerStackView.addArrangedSubview(helperLabel) @@ -321,11 +297,11 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { // Add arranged subviews to textFieldStackView contentStackView.addArrangedSubview(containerView) contentStackView.addArrangedSubview(bottomContainerView) - + // Add arranged subviews to mainStackView mainStackView.addArrangedSubview(titleLabel) mainStackView.addArrangedSubview(contentStackView) - + // Initial position of the helper label updateHelperTextPosition() @@ -333,6 +309,38 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { titleLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() errorLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() helperLabel.textColorConfiguration = secondaryColorConfiguration.eraseToAnyColorable() + + containerView.bridge_accessibilityLabelBlock = { [weak self] in + guard let self else { return "" } + var accessibilityLabels = [String]() + + if let text = titleLabel.text?.trimmingCharacters(in: .whitespaces) { + accessibilityLabels.append(text) + } + if isReadOnly { + accessibilityLabels.append("read only") + } + if !isEnabled { + accessibilityLabels.append("dimmed") + } + if let errorText, showError { + accessibilityLabels.append("error, \(errorText)") + } + + accessibilityLabels.append("\(Self.self)") + + return accessibilityLabels.joined(separator: ", ") + } + + containerView.bridge_accessibilityHintBlock = { [weak self] in + guard let self else { return "" } + return isReadOnly || !isEnabled ? "" : accessibilityHintText + } + + containerView.bridge_accessibilityValueBlock = { [weak self] in + guard let self else { return "" } + return value + } } /// Updates the UI @@ -472,13 +480,6 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { } } - open override func updateAccessibility() { - super.updateAccessibility() - containerView.accessibilityLabel = accessibilityLabelText - containerView.accessibilityHint = isReadOnly || !isEnabled ? "" : accessibilityHintText - containerView.accessibilityValue = value - } - open override var accessibilityElements: [Any]? { get { var elements = [Any]() diff --git a/VDS/Components/TextFields/InputField/TextField.swift b/VDS/Components/TextFields/InputField/TextField.swift index 81e749b2..0108c874 100644 --- a/VDS/Components/TextFields/InputField/TextField.swift +++ b/VDS/Components/TextFields/InputField/TextField.swift @@ -67,8 +67,6 @@ open class TextField: UITextField, ViewProtocol, Errorable { /// Will determine if a scaled font should be used for the titleLabel font. open var useScaledFont: Bool = false { didSet { setNeedsUpdate() } } - 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 bbb7b329..ed622f90 100644 --- a/VDS/Components/TextFields/TextArea/TextView.swift +++ b/VDS/Components/TextFields/TextArea/TextView.swift @@ -149,8 +149,6 @@ open class TextView: UITextView, ViewProtocol, Errorable { //-------------------------------------------------- // MARK: - Accessibility //-------------------------------------------------- - open var shouldUpdateAccessibility: Bool = true - open var accessibilityAction: ((TextView) -> Void)? private var _isAccessibilityElement: Bool = false diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index d600f5dd..546d8d05 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -266,6 +266,11 @@ open class TileContainerBase: Control where Padding backgroundImageView.layer.cornerRadius = cornerRadius highlightView.layer.cornerRadius = cornerRadius clipsToBounds = true + + containerView.bridge_isAccessibilityElementBlock = { [weak self] in self?.onClickSubscriber != nil } + containerView.accessibilityHint = "Double tap to open." + containerView.accessibilityLabel = nil + } /// Overriden to take the hit if there is an onClickSubscriber and the view is not a UIControl @@ -338,13 +343,6 @@ open class TileContainerBase: Control where Padding } } - open override func updateAccessibility() { - super.updateAccessibility() - containerView.isAccessibilityElement = onClickSubscriber != nil - containerView.accessibilityHint = "Double tap to open." - containerView.accessibilityLabel = nil - } - open override var accessibilityElements: [Any]? { get { var items = [Any]() diff --git a/VDS/Components/TitleLockup/TitleLockup.swift b/VDS/Components/TitleLockup/TitleLockup.swift index af699b5c..bc5c4c3a 100644 --- a/VDS/Components/TitleLockup/TitleLockup.swift +++ b/VDS/Components/TitleLockup/TitleLockup.swift @@ -262,20 +262,21 @@ open class TitleLockup: View { //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- - open override func updateAccessibility() { - super.updateAccessibility() - var elements = [Any]() - if eyebrowModel != nil { - elements.append(eyebrowLabel) + open override var accessibilityElements: [Any]? { + get { + var elements = [Any]() + if eyebrowModel != nil { + elements.append(eyebrowLabel) + } + if titleModel != nil { + elements.append(titleLabel) + } + if subTitleModel != nil { + elements.append(subTitleLabel) + } + return elements.count > 0 ? elements : nil } - if titleModel != nil { - elements.append(titleLabel) - } - if subTitleModel != nil { - elements.append(subTitleLabel) - } - setAccessibilityLabel(for: elements.compactMap({$0 as? UIView})) - accessibilityElements = elements.count > 0 ? elements : nil + set {} } /// Resets to default settings. diff --git a/VDS/Components/Toggle/Toggle.swift b/VDS/Components/Toggle/Toggle.swift index 6518e8db..aaa411de 100644 --- a/VDS/Components/Toggle/Toggle.swift +++ b/VDS/Components/Toggle/Toggle.swift @@ -207,6 +207,14 @@ open class Toggle: Control, Changeable, FormFieldable { label.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor) ] + bridge_accessibilityValueBlock = { [weak self] in + guard let self else { return "" } + if showText { + return isSelected ? onText : offText + } else { + return isSelected ? "On" : "Off" + } + } } /// Resets to default settings. @@ -238,16 +246,6 @@ open class Toggle: Control, Changeable, FormFieldable { toggleView.isEnabled = isEnabled toggleView.isOn = isOn } - - /// Used to update any Accessibility properties. - open override func updateAccessibility() { - super.updateAccessibility() - if showText { - accessibilityValue = isSelected ? onText : offText - } else { - accessibilityValue = isSelected ? "On" : "Off" - } - } /// This will change the state of the Selector and execute the actionBlock if provided. open func toggle() { diff --git a/VDS/Components/Toggle/ToggleView.swift b/VDS/Components/Toggle/ToggleView.swift index 4889ed40..935ed519 100644 --- a/VDS/Components/Toggle/ToggleView.swift +++ b/VDS/Components/Toggle/ToggleView.swift @@ -153,7 +153,7 @@ open class ToggleView: Control, Changeable, FormFieldable { // Update shadow layers frames to match the view's bounds knobView.layer.insertSublayer(shadowLayer1, at: 0) knobView.layer.insertSublayer(shadowLayer2, at: 0) - + accessibilityLabel = "Toggle" } /// Resets to default settings. @@ -176,13 +176,6 @@ open class ToggleView: Control, Changeable, FormFieldable { updateToggle() } - - /// Used to update any Accessibility properties. - open override func updateAccessibility() { - super.updateAccessibility() - - accessibilityLabel = "Toggle" - } /// This will change the state of the Selector and execute the actionBlock if provided. open func toggle() { diff --git a/VDS/Components/Tooltip/Tooltip.swift b/VDS/Components/Tooltip/Tooltip.swift index 0875ee64..f07fb1be 100644 --- a/VDS/Components/Tooltip/Tooltip.swift +++ b/VDS/Components/Tooltip/Tooltip.swift @@ -138,6 +138,24 @@ open class Tooltip: Control, TooltipLaunchable { contentView: tooltip.contentView), presenter: self) } + + bridge_accessibilityLabelBlock = { [weak self] in + guard let self else { return "" } + var label = title + if label == nil { + label = content + } + if let label, !label.isEmpty { + return label + } else { + return "Modal" + } + } + + bridge_accessibilityHintBlock = { [weak self] in + guard let self else { return "" } + return isEnabled ? "Double tap to open." : "" + } } /// Resets to default settings. @@ -163,23 +181,7 @@ open class Tooltip: Control, TooltipLaunchable { //get the color for the image icon.color = iconColorConfiguration.getColor(self) } - - /// Used to update any Accessibility properties. - open override func updateAccessibility() { - super.updateAccessibility() - var label = title - if label == nil { - label = content - } - if let label, !label.isEmpty { - accessibilityLabel = label - } else { - accessibilityLabel = "Modal" - } - accessibilityHint = isEnabled ? "Double tap to open." : "" - } - public static func accessibleText(for title: String?, content: String?, closeButtonText: String) -> String { var label = "" if let title { diff --git a/VDS/Components/Tooltip/TooltipDialog.swift b/VDS/Components/Tooltip/TooltipDialog.swift index 1a6e192d..0650a808 100644 --- a/VDS/Components/Tooltip/TooltipDialog.swift +++ b/VDS/Components/Tooltip/TooltipDialog.swift @@ -219,15 +219,19 @@ open class TooltipDialog: View, UIScrollViewDelegate { /// Used to update any Accessibility properties. open override func updateAccessibility() { super.updateAccessibility() - primaryAccessibilityElement.accessibilityHint = "Double tap on the \(tooltipModel.closeButtonText) button to close." primaryAccessibilityElement.accessibilityFrameInContainerSpace = .init(origin: .zero, size: frame.size) + } + + open override var accessibilityElements: [Any]? { + get { + var elements: [Any] = [primaryAccessibilityElement] + contentStackView.arrangedSubviews.forEach{ elements.append($0) } + elements.append(closeButton) - var elements: [Any] = [primaryAccessibilityElement] - contentStackView.arrangedSubviews.forEach{ elements.append($0) } - elements.append(closeButton) - - accessibilityElements = elements + return elements + } + set {} } } diff --git a/VDS/Protocols/AccessibilityUpdatable.swift b/VDS/Protocols/AccessibilityUpdatable.swift index 07bd429b..de46f867 100644 --- a/VDS/Protocols/AccessibilityUpdatable.swift +++ b/VDS/Protocols/AccessibilityUpdatable.swift @@ -6,3 +6,90 @@ // import Foundation +import UIKit + +public protocol AccessibilityUpdatable { + var bridge_isAccessibilityElementBlock: AXBoolReturnBlock? { get set } + + var bridge_accessibilityLabelBlock: AXStringReturnBlock? { get set } + + var bridge_accessibilityValueBlock: AXStringReturnBlock? { get set } + + var bridge_accessibilityHintBlock: AXStringReturnBlock? { get set } + + var bridge_accessibilityActivateBlock: AXBoolReturnBlock? { get set } + +} + +private struct AccessibilityBridge { + static var isAccessibilityElementBlockKey: UInt8 = 0 + static var activateBlockKey: UInt8 = 1 + static var valueBlockKey: UInt8 = 2 + static var hintBlockKey: UInt8 = 3 + static var labelBlockKey: UInt8 = 4 +} + +extension AccessibilityUpdatable where Self: NSObject { + + public var bridge_isAccessibilityElementBlock: AXBoolReturnBlock? { + get { + return objc_getAssociatedObject(self, &AccessibilityBridge.isAccessibilityElementBlockKey) as? AXBoolReturnBlock + } + set { + objc_setAssociatedObject(self, &AccessibilityBridge.isAccessibilityElementBlockKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) +// if #available(iOS 17, *) { +// self.isAccessibilityElementBlock = newValue +// } + } + } + + public var bridge_accessibilityActivateBlock: AXBoolReturnBlock? { + get { + return objc_getAssociatedObject(self, &AccessibilityBridge.activateBlockKey) as? AXBoolReturnBlock + } + set { + objc_setAssociatedObject(self, &AccessibilityBridge.activateBlockKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) +// if #available(iOS 17, *) { +// self.accessibilityActivateBlock = newValue +// } + } + } + + public var bridge_accessibilityValueBlock: AXStringReturnBlock? { + get { + return objc_getAssociatedObject(self, &AccessibilityBridge.valueBlockKey) as? AXStringReturnBlock + } + set { + objc_setAssociatedObject(self, &AccessibilityBridge.valueBlockKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) +// if #available(iOS 17, *) { +// self.accessibilityValueBlock = newValue +// } + } + } + + public var bridge_accessibilityHintBlock: AXStringReturnBlock? { + get { + return objc_getAssociatedObject(self, &AccessibilityBridge.hintBlockKey) as? AXStringReturnBlock + } + set { + objc_setAssociatedObject(self, &AccessibilityBridge.hintBlockKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) +// if #available(iOS 17, *) { +// self.accessibilityHintBlock = newValue +// } + } + } + + public var bridge_accessibilityLabelBlock: AXStringReturnBlock? { + get { + return objc_getAssociatedObject(self, &AccessibilityBridge.labelBlockKey) as? AXStringReturnBlock + } + set { + objc_setAssociatedObject(self, &AccessibilityBridge.labelBlockKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) +// if #available(iOS 17, *) { +// self.accessibilityLabelBlock = newValue +// } + } + } + +} + diff --git a/VDS/Protocols/Groupable.swift b/VDS/Protocols/Groupable.swift index 773362b7..b2f95782 100644 --- a/VDS/Protocols/Groupable.swift +++ b/VDS/Protocols/Groupable.swift @@ -9,6 +9,4 @@ import Foundation public protocol Groupable: Control { - /// Property used to add context to the Grouping of a set. - var accessibilityValueText: String? { get set } } diff --git a/VDS/Protocols/ViewProtocol.swift b/VDS/Protocols/ViewProtocol.swift index 8018e260..c7cba091 100644 --- a/VDS/Protocols/ViewProtocol.swift +++ b/VDS/Protocols/ViewProtocol.swift @@ -9,16 +9,13 @@ import Foundation import UIKit import Combine -public protocol ViewProtocol: AnyObject, Initable, Resettable, Enabling, Surfaceable { +public protocol ViewProtocol: AnyObject, Initable, Resettable, Enabling, Surfaceable, AccessibilityUpdatable { /// Set of Subscribers for any Publishers for this Control. var subscribers: Set { get set } /// 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 } - /// Used for setting an implementation for the default Accessible Action var accessibilityAction: ((Self) -> Void)? { get set } @@ -42,9 +39,7 @@ extension ViewProtocol { if shouldUpdateView { shouldUpdateView = false updateView() - if shouldUpdateAccessibility { - updateAccessibility() - } + updateAccessibility() shouldUpdateView = true } } @@ -70,86 +65,3 @@ extension ViewProtocol where Self: UIControl { }).store(in: &subscribers) } } - -public protocol AccessibilityUpdatable { - // Basic accessibility - var bridge_isAccessibilityElementBlock: AXBoolReturnBlock? { get set } - - var bridge_accessibilityLabelBlock: AXStringReturnBlock? { get set } - - var bridge_accessibilityValueBlock: AXStringReturnBlock? { get set } - - var bridge_accessibilityHintBlock: AXStringReturnBlock? { get set } - - var bridge_accessibilityActivateBlock: AXBoolReturnBlock? { get set } - -} - -extension NSObject: AccessibilityUpdatable { - static var isAccessibilityElementBlockKey: UInt8 = 0 - static var activateBlockKey: UInt8 = 1 - static var valueBlockKey: UInt8 = 2 - static var hintBlockKey: UInt8 = 3 - static var labelBlockKey: UInt8 = 4 - - public var bridge_isAccessibilityElementBlock: AXBoolReturnBlock? { - get { - return objc_getAssociatedObject(self, &NSObject.isAccessibilityElementBlockKey) as? AXBoolReturnBlock - } - set { - objc_setAssociatedObject(self, &NSObject.isAccessibilityElementBlockKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) -// if #available(iOS 17, *) { -// self.isAccessibilityElementBlock = newValue -// } - } - } - - public var bridge_accessibilityActivateBlock: AXBoolReturnBlock? { - get { - return objc_getAssociatedObject(self, &NSObject.activateBlockKey) as? AXBoolReturnBlock - } - set { - objc_setAssociatedObject(self, &NSObject.activateBlockKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) -// if #available(iOS 17, *) { -// self.accessibilityActivateBlock = newValue -// } - } - } - - public var bridge_accessibilityValueBlock: AXStringReturnBlock? { - get { - return objc_getAssociatedObject(self, &NSObject.valueBlockKey) as? AXStringReturnBlock - } - set { - objc_setAssociatedObject(self, &NSObject.valueBlockKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) -// if #available(iOS 17, *) { -// self.accessibilityValueBlock = newValue -// } - } - } - - public var bridge_accessibilityHintBlock: AXStringReturnBlock? { - get { - return objc_getAssociatedObject(self, &NSObject.hintBlockKey) as? AXStringReturnBlock - } - set { - objc_setAssociatedObject(self, &NSObject.hintBlockKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) -// if #available(iOS 17, *) { -// self.accessibilityHintBlock = newValue -// } - } - } - - public var bridge_accessibilityLabelBlock: AXStringReturnBlock? { - get { - return objc_getAssociatedObject(self, &NSObject.labelBlockKey) as? AXStringReturnBlock - } - set { - objc_setAssociatedObject(self, &NSObject.labelBlockKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) -// if #available(iOS 17, *) { -// self.accessibilityLabelBlock = newValue -// } - } - } - -}