From 46fb714d3495331b375db1e35b6b8c23cef3ba97 Mon Sep 17 00:00:00 2001 From: Krishna Kishore Bandaru Date: Wed, 27 Sep 2023 20:53:50 +0530 Subject: [PATCH] added heading level rotor --- .../Accessibility/AccessibilityHandler.swift | 108 +++++++++++------- .../Atomic/Atoms/Buttons/ButtonModel.swift | 3 + .../Atomic/Atoms/Views/Label/Label.swift | 7 ++ .../Protocols/MoleculeViewProtocol.swift | 2 +- MVMCoreUI/BaseClasses/Button.swift | 4 + 5 files changed, 81 insertions(+), 43 deletions(-) diff --git a/MVMCoreUI/Accessibility/AccessibilityHandler.swift b/MVMCoreUI/Accessibility/AccessibilityHandler.swift index af9c13a5..aeba4970 100644 --- a/MVMCoreUI/Accessibility/AccessibilityHandler.swift +++ b/MVMCoreUI/Accessibility/AccessibilityHandler.swift @@ -197,18 +197,32 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { enum RotorType: String, CaseIterable { case button = "Buttons" + case header = "Header" var trait: UIAccessibilityTraits { switch self { case .button: return .button + case .header: + return .header } } + + func getUIAccessibilityCustomRotor(itemSearch: @escaping UIAccessibilityCustomRotor.Search) -> UIAccessibilityCustomRotor? { + var accessibilityCustomRotor: UIAccessibilityCustomRotor? + switch self { + case .header: + accessibilityCustomRotor = UIAccessibilityCustomRotor(systemType: .heading, itemSearch: itemSearch) + default: + accessibilityCustomRotor = UIAccessibilityCustomRotor(name: rawValue, itemSearch: itemSearch) + } + return accessibilityCustomRotor + } } public var anyCancellable: Set = [] private var delegateObj: MVMCoreUIDelegateObject? - private var currentRotorIndex: Int = 0 + private var rotorIndexes: [RotorType: Int] = [:] required public init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { } @@ -239,43 +253,66 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { } } - //MARK: - Private Methods + //MARK: - Accessibility Methods + + private func updateAccessibilityViews(_ delegateObject: MVMCoreUIDelegateObject?) { + var accessibilityElements: [Any?] = [MVMCoreUISplitViewController.main()?.topAlertView] + if let managerController = (delegateObject?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController { + accessibilityElements.append(managerController.navigationController) + accessibilityElements.append(managerController.tabs) + accessibilityElements.append(contentsOf: managerController.view.subviews) + accessibilityElements.append(MVMCoreUISplitViewController.main()?.tabBar) + managerController.view.accessibilityElements = accessibilityElements.compactMap { $0 } + } else if let controller = delegateObject?.moleculeDelegate as? UIViewController { + accessibilityElements.append(controller.navigationController) + accessibilityElements.append(contentsOf: controller.view.subviews.reversed()) + accessibilityElements.append(MVMCoreUISplitViewController.main()?.tabBar) + controller.view.accessibilityElements = accessibilityElements.compactMap { $0 } + } + } + + //MARK: - Rotor Methods private func identifyAndPrepareRotors() { var rotorElements: [UIAccessibilityCustomRotor] = [] let currentViewController = ((delegateObj?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController) ?? (delegateObj?.moleculeDelegate as? ViewController) for type in RotorType.allCases { - if let elements = getTraitMappedElements(template: currentViewController, type: type), - let rotor = createRotor(elements, for: type) { + if let elements = getTraitMappedElements(currentViewController, type: type), + let rotor = createRotor(elements, for: type) { rotorElements.append(rotor) } } currentViewController?.navigationController?.accessibilityCustomRotors = rotorElements } - private func getTraitMappedElements(template: ViewController?, type: RotorType) -> [Any]? { - var accessibilityElements: [Any?]? = template?.navigationItem.leftBarButtonItems ?? [] - accessibilityElements?.append(contentsOf: template?.navigationItem.rightBarButtonItems ?? []) + private func getTraitMappedElements(_ template: ViewController?, type: RotorType) -> [Any]? { + var accessibilityElements: [Any?] = [] + switch type { + case .button: + accessibilityElements.append(contentsOf: template?.navigationItem.leftBarButtonItems ?? []) + accessibilityElements.append(contentsOf: template?.navigationItem.rightBarButtonItems ?? []) + case .header: + accessibilityElements.append(template?.navigationItem.titleView) + } if let tabs = (template as? SubNavManagerController)?.tabs { - accessibilityElements?.append(contentsOf: tabs.subviews.filter { + accessibilityElements.append(contentsOf: tabs.subviews.filter { $0.accessibilityTraits.contains(type.trait) }) } if let rotorElements = getRotorElementsFrom(template: template, type: type) { - accessibilityElements?.append(contentsOf: rotorElements) + accessibilityElements.append(contentsOf: rotorElements) } if let tabBarHidden = (template as? TabPageModelProtocol)?.tabBarHidden, !tabBarHidden { - accessibilityElements?.append(contentsOf: (MVMCoreUISplitViewController.main()?.tabBar?.subviews ?? []).filter { + accessibilityElements.append(contentsOf: (MVMCoreUISplitViewController.main()?.tabBar?.subviews ?? []).filter { $0.accessibilityTraits.contains(type.trait) }) } - return accessibilityElements?.compactMap { $0 } + return accessibilityElements.compactMap { $0 } } private func getRotorElementsFrom(template: ViewController?, type: RotorType) -> [Any]? { if let currentViewController = template as? MoleculeListTemplate, - let templateModel = currentViewController.templateModel, - let tableView = currentViewController.tableView { //List templates - + let templateModel = currentViewController.templateModel, + let tableView = currentViewController.tableView { //List templates var rotorElements: [(model: MoleculeModelProtocol, indexPath: IndexPath)] = [] var currentIndexPath: IndexPath? @@ -301,7 +338,7 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { let otherInteractiveElements = currentViewController.view?.getMoleculeViews(excludedViews: [tableView, tableView.tableFooterView, tableView.tableHeaderView] .compactMap { $0 }) { (subView: MoleculeViewProtocol, _) in subView.accessibilityTraits.contains(type.trait) - } as? [Any] ?? [] + } as? [Any] ?? [] return headerViewElements + otherInteractiveElements + (rotorElements as [Any]) + footerViewElements } else if let currentViewController = template as? MoleculeStackTemplate { //Stack templates @@ -313,22 +350,24 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { } private func createRotor(_ elements: [Any], for type: RotorType) -> UIAccessibilityCustomRotor? { - guard elements.isEmpty, let tableView = (delegateObj?.moleculeListDelegate as? MoleculeListTemplate)?.tableView else { return nil } - return UIAccessibilityCustomRotor(name: type.rawValue) { [weak self] predicate in + guard !elements.isEmpty else { return nil } + return type.getUIAccessibilityCustomRotor { [weak self] predicate in guard let self else { return UIAccessibilityCustomRotorItemResult() } + var rotorIndex = self.rotorIndexes[type] ?? 0 if predicate.searchDirection == .next { - self.currentRotorIndex += 1 - if self.currentRotorIndex > elements.count { - self.currentRotorIndex = 1 + rotorIndex += 1 + if rotorIndex > elements.count { + rotorIndex = 1 } } else { - self.currentRotorIndex -= 1 - if self.currentRotorIndex <= 0 { - self.currentRotorIndex = elements.count + rotorIndex -= 1 + if rotorIndex <= 0 { + rotorIndex = elements.count } } - var rotorElement = elements[self.currentRotorIndex - 1] - if let element = rotorElement as? (model: MoleculeModelProtocol, indexPath: IndexPath) { + var rotorElement = elements[rotorIndex - 1] + if let tableView = (self.delegateObj?.moleculeListDelegate as? MoleculeListTemplate)?.tableView, + let element = rotorElement as? (model: MoleculeModelProtocol, indexPath: IndexPath) { //for List templates tableView.scrollToRow(at: element.indexPath, at: .middle, animated: false) rotorElement = tableView.cellForRow(at: element.indexPath)?.getMoleculeViews { (subView: MoleculeViewProtocol, stop: inout Bool) in guard subView.accessibilityTraits.contains(type.trait), (subView as? MoleculeViewModelProtocol)?.moleculeModel?.id == element.model.id else { return false } @@ -336,26 +375,11 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior { return true }.first as Any } - UIAccessibility.post(notification: .layoutChanged, argument: rotorElement) + self.rotorIndexes[type] = rotorIndex + AccessibilityHandler.shared()?.post(notification: .layoutChanged, argument: rotorElement) return UIAccessibilityCustomRotorItemResult(targetElement: rotorElement as! NSObjectProtocol, targetRange: nil) } } - - private func updateAccessibilityViews(_ delegateObject: MVMCoreUIDelegateObject?) { - var accessibilityElements: [Any?] = [MVMCoreUISplitViewController.main()?.topAlertView] - if let managerController = (delegateObject?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController { - accessibilityElements.append(managerController.navigationController) - accessibilityElements.append(managerController.tabs) - accessibilityElements.append(contentsOf: managerController.view.subviews) - accessibilityElements.append(MVMCoreUISplitViewController.main()?.tabBar) - managerController.view.accessibilityElements = accessibilityElements.compactMap { $0 } - } else if let controller = delegateObject?.moleculeDelegate as? UIViewController { - accessibilityElements.append(controller.navigationController) - accessibilityElements.append(contentsOf: controller.view.subviews.reversed()) - accessibilityElements.append(MVMCoreUISplitViewController.main()?.tabBar) - controller.view.accessibilityElements = accessibilityElements.compactMap { $0 } - } - } } // MARK: - Helpers diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift index 08afefde..92b50d29 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/ButtonModel.swift @@ -35,6 +35,7 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat public var size: Styler.Button.Size? = .standard public var groupName: String = "" public var inverted: Bool = false + public var accessibilityTraits: UIAccessibilityTraits? public lazy var enabledColors: FacadeElements = (fill: enabled_fillColor(), text: enabled_textColor(), @@ -195,6 +196,7 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat case disabledTextColor case disabledBorderColor case width + case accessibilityTraits } //-------------------------------------------------- @@ -207,6 +209,7 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier) accessibilityText = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityText) + accessibilityTraits = try typeContainer.decodeIfPresent(UIAccessibilityTraits.self, forKey: .accessibilityTraits) title = try typeContainer.decode(String.self, forKey: .title) action = try typeContainer.decodeModel(codingKey: .action) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift index 01614f31..119fc66b 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift @@ -44,6 +44,7 @@ public typealias ActionBlock = () -> () public var shouldMaskWhileRecording: Bool = false + private var model: MoleculeModelProtocol? //------------------------------------------------------ // MARK: - Multi-Action Text //------------------------------------------------------ @@ -408,6 +409,7 @@ public typealias ActionBlock = () -> () attributedText = attributedString originalAttributedString = attributedText } + self.model = labelModel } @objc public static func setUILabel(_ label: UILabel?, withJSON json: [AnyHashable: Any]?, delegate: DelegateObject?, additionalData: [AnyHashable: Any]?) { @@ -1027,3 +1029,8 @@ func validateAttribute(range: NSRange, in string: NSAttributedString, type: Stri return range } + +extension Label: MoleculeViewModelProtocol { + + public var moleculeModel: MoleculeModelProtocol? { model } +} diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift index 3a6bc8f6..1d7ebd03 100644 --- a/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift +++ b/MVMCoreUI/Atomic/Protocols/MoleculeViewProtocol.swift @@ -109,7 +109,7 @@ public protocol MoleculeViewModelProtocol: UIView { public extension MoleculeViewModelProtocol { - public var moleculeModel: MoleculeModelProtocol? { + var moleculeModel: MoleculeModelProtocol? { get { nil } } } diff --git a/MVMCoreUI/BaseClasses/Button.swift b/MVMCoreUI/BaseClasses/Button.swift index 7450b716..c744452e 100644 --- a/MVMCoreUI/BaseClasses/Button.swift +++ b/MVMCoreUI/BaseClasses/Button.swift @@ -107,6 +107,10 @@ public typealias ButtonAction = (Button) -> () isEnabled = model.enabled } + if let accessibilityTraits = model.accessibilityTraits { + self.accessibilityTraits = accessibilityTraits + } + guard let model = model as? ButtonModelProtocol else { return } set(with: model.action, delegateObject: delegateObject, additionalData: additionalData)