updated with code review comments & created accessibility handler behaviour
This commit is contained in:
parent
c538df828e
commit
32ba75e731
@ -168,6 +168,7 @@
|
|||||||
526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */; };
|
526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */; };
|
||||||
52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */; };
|
52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */; };
|
||||||
52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */; };
|
52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */; };
|
||||||
|
71033C002AB60AEA0038D7A4 /* MVMCoreUISession+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71033BFE2AB609530038D7A4 /* MVMCoreUISession+Extension.swift */; };
|
||||||
7199C8162A4F3A64001568B7 /* AccessibilityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */; };
|
7199C8162A4F3A64001568B7 /* AccessibilityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */; };
|
||||||
8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */; };
|
8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */; };
|
||||||
8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */; };
|
8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */; };
|
||||||
@ -755,6 +756,7 @@
|
|||||||
526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnCompareChanges.swift; sourceTree = "<group>"; };
|
526A265D240D200500B0D828 /* ListTwoColumnCompareChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnCompareChanges.swift; sourceTree = "<group>"; };
|
||||||
52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethod.swift; sourceTree = "<group>"; };
|
52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethod.swift; sourceTree = "<group>"; };
|
||||||
52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethodModel.swift; sourceTree = "<group>"; };
|
52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAndPaymentMethodModel.swift; sourceTree = "<group>"; };
|
||||||
|
71033BFE2AB609530038D7A4 /* MVMCoreUISession+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreUISession+Extension.swift"; sourceTree = "<group>"; };
|
||||||
7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityHandler.swift; sourceTree = "<group>"; };
|
7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityHandler.swift; sourceTree = "<group>"; };
|
||||||
8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalDataModel.swift; sourceTree = "<group>"; };
|
8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalDataModel.swift; sourceTree = "<group>"; };
|
||||||
8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalData.swift; sourceTree = "<group>"; };
|
8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableTotalData.swift; sourceTree = "<group>"; };
|
||||||
@ -2277,6 +2279,7 @@
|
|||||||
D2B18B912361E65A00A9AEDC /* CoreUIObject.swift */,
|
D2B18B912361E65A00A9AEDC /* CoreUIObject.swift */,
|
||||||
D29DF27721E7A533003B2FB9 /* MVMCoreUISession.h */,
|
D29DF27721E7A533003B2FB9 /* MVMCoreUISession.h */,
|
||||||
D29DF27821E7A533003B2FB9 /* MVMCoreUISession.m */,
|
D29DF27821E7A533003B2FB9 /* MVMCoreUISession.m */,
|
||||||
|
71033BFE2AB609530038D7A4 /* MVMCoreUISession+Extension.swift */,
|
||||||
D29DF27321E79E81003B2FB9 /* MVMCoreUILoggingHandler.h */,
|
D29DF27321E79E81003B2FB9 /* MVMCoreUILoggingHandler.h */,
|
||||||
D29DF27421E79E81003B2FB9 /* MVMCoreUILoggingHandler.m */,
|
D29DF27421E79E81003B2FB9 /* MVMCoreUILoggingHandler.m */,
|
||||||
AFA4933E29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift */,
|
AFA4933E29E874F0001A9663 /* MVMCoreUILoggingDelegateProtocol.swift */,
|
||||||
@ -3093,6 +3096,7 @@
|
|||||||
D29C559025C095210082E7D6 /* Video.swift in Sources */,
|
D29C559025C095210082E7D6 /* Video.swift in Sources */,
|
||||||
D264FA90243BCE6800D98315 /* ThreeLayerCollectionViewController.swift in Sources */,
|
D264FA90243BCE6800D98315 /* ThreeLayerCollectionViewController.swift in Sources */,
|
||||||
AA104B1C24474A76004D2810 /* HeadersH2ButtonsModel.swift in Sources */,
|
AA104B1C24474A76004D2810 /* HeadersH2ButtonsModel.swift in Sources */,
|
||||||
|
71033C002AB60AEA0038D7A4 /* MVMCoreUISession+Extension.swift in Sources */,
|
||||||
0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */,
|
0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */,
|
||||||
AAE7270C24AC8B8500A3ED0E /* HeadersH2CaretLinkModel.swift in Sources */,
|
AAE7270C24AC8B8500A3ED0E /* HeadersH2CaretLinkModel.swift in Sources */,
|
||||||
BB6C6AC824225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift in Sources */,
|
BB6C6AC824225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift in Sources */,
|
||||||
|
|||||||
@ -11,44 +11,13 @@ import Combine
|
|||||||
import MVMCore
|
import MVMCore
|
||||||
import WebKit
|
import WebKit
|
||||||
|
|
||||||
public enum AccessibilityNotificationType: String, Codable {
|
|
||||||
|
|
||||||
case controllerChanged, layoutChanged, screenChanged, announcement
|
|
||||||
|
|
||||||
//TODO: - Foucs is shifting to respective element only if we add delay only on new viewcontroller appear. Need to investigate futher.
|
|
||||||
//https://developer.apple.com/forums/thread/132699,
|
|
||||||
//https://developer.apple.com/forums/thread/655359
|
|
||||||
//By default from iOS 13+ focus is getting shifted to first interactive element inside viewcontroller not to the navigationitem left barbutton item so posting layoutChanged notification with delay to push to leftbarbutton item on new screen push
|
|
||||||
var delay: Double {
|
|
||||||
switch self {
|
|
||||||
case .controllerChanged:
|
|
||||||
return 1.5
|
|
||||||
default:
|
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var accessibilityNotification: UIAccessibility.Notification {
|
|
||||||
switch self {
|
|
||||||
case .announcement:
|
|
||||||
return .announcement
|
|
||||||
case .screenChanged:
|
|
||||||
return .screenChanged
|
|
||||||
case .layoutChanged, .controllerChanged:
|
|
||||||
return .layoutChanged
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public typealias ArgumentHandler = ((NavigationOperationType?) -> Any?)
|
|
||||||
|
|
||||||
public class AccessbilityOperation: MVMCoreOperation {
|
public class AccessbilityOperation: MVMCoreOperation {
|
||||||
|
|
||||||
private let argument: Any?
|
private let argument: Any?
|
||||||
private let notificationType: AccessibilityNotificationType
|
private let notificationType: UIAccessibility.Notification
|
||||||
private var timerSource: DispatchSourceTimer?
|
private var timerSource: DispatchSourceTimer?
|
||||||
|
|
||||||
public init(notificationType: AccessibilityNotificationType, argument: Any?) {
|
public init(notificationType: UIAccessibility.Notification, argument: Any?) {
|
||||||
self.notificationType = notificationType
|
self.notificationType = notificationType
|
||||||
self.argument = argument
|
self.argument = argument
|
||||||
}
|
}
|
||||||
@ -61,15 +30,15 @@ public class AccessbilityOperation: MVMCoreOperation {
|
|||||||
timerSource = DispatchSource.makeTimerSource()
|
timerSource = DispatchSource.makeTimerSource()
|
||||||
timerSource?.setEventHandler {
|
timerSource?.setEventHandler {
|
||||||
Task { @MainActor [weak self] in
|
Task { @MainActor [weak self] in
|
||||||
if !(self?.isCancelled ?? false), let notification = self?.notificationType.accessibilityNotification {
|
guard let self = self, !self.isCancelled else {
|
||||||
UIAccessibility.post(notification: notification, argument: self?.argument)
|
|
||||||
self?.markAsFinished()
|
|
||||||
} else {
|
|
||||||
self?.stop()
|
self?.stop()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
UIAccessibility.post(notification: self.notificationType, argument: self.argument)
|
||||||
|
self.markAsFinished()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timerSource?.schedule(deadline: .now() + notificationType.delay)
|
timerSource?.schedule(deadline: .now())
|
||||||
timerSource?.activate()
|
timerSource?.activate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,17 +49,16 @@ public class AccessbilityOperation: MVMCoreOperation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum NavigationOperationType { case `default`, tab }
|
|
||||||
|
|
||||||
open class AccessibilityHandler {
|
open class AccessibilityHandler {
|
||||||
|
|
||||||
public static func shared() -> Self? {
|
public static func shared() -> Self? {
|
||||||
guard let shared = CoreUIObject.sharedInstance()?.accessibilityHandler else { return nil }
|
guard let shared = CoreUIObject.sharedInstance()?.accessibilityHandler else { return nil }
|
||||||
return MVMCoreActionUtility.fatalClassCheck(object: shared)
|
return MVMCoreActionUtility.fatalClassCheck(object: shared)
|
||||||
}
|
}
|
||||||
public weak var delegate: MVMCoreViewControllerProtocol?
|
|
||||||
public var previousAccessiblityElement: Any?
|
public var previousAccessiblityElement: Any?
|
||||||
public var anyCancellable: Set<AnyCancellable> = []
|
public var anyCancellable: Set<AnyCancellable> = []
|
||||||
|
public weak var delegate: MVMCoreViewControllerProtocol?
|
||||||
|
|
||||||
private var accessibilityOperationQueue: OperationQueue = {
|
private var accessibilityOperationQueue: OperationQueue = {
|
||||||
let queue = OperationQueue()
|
let queue = OperationQueue()
|
||||||
@ -100,16 +68,16 @@ open class AccessibilityHandler {
|
|||||||
private var accessibilityId: String?
|
private var accessibilityId: String?
|
||||||
private var announcementText: String?
|
private var announcementText: String?
|
||||||
private var hasTopNotitificationInPage: Bool = false
|
private var hasTopNotitificationInPage: Bool = false
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
registerWithNotificationCenter()
|
registerWithResponseLoaded()
|
||||||
registerForPageChanges()
|
registerForPageChanges()
|
||||||
registerForFocusChanges()
|
registerForFocusChanges()
|
||||||
registerForTopNotificationsChanges()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers with the notification center to know when json is updated.
|
// MARK: - Register with Accessibility Handler listeners
|
||||||
private func registerWithNotificationCenter() {
|
/// Registers with the notification center to know when json is updated and to capture previous accessbility focused id & announcment text
|
||||||
|
private func registerWithResponseLoaded() {
|
||||||
NotificationCenter.default.publisher(for: NSNotification.Name(rawValue: NotificationResponseLoaded))
|
NotificationCenter.default.publisher(for: NSNotification.Name(rawValue: NotificationResponseLoaded))
|
||||||
.sink { [weak self] notification in
|
.sink { [weak self] notification in
|
||||||
self?.accessibilityId = (notification.userInfo?[String(describing: MVMCoreLoadObject.self)] as? MVMCoreLoadObject)?.pageJSON?.optionalStringForKey("accessibilityId")
|
self?.accessibilityId = (notification.userInfo?[String(describing: MVMCoreLoadObject.self)] as? MVMCoreLoadObject)?.pageJSON?.optionalStringForKey("accessibilityId")
|
||||||
@ -117,20 +85,15 @@ open class AccessibilityHandler {
|
|||||||
}.store(in: &anyCancellable)
|
}.store(in: &anyCancellable)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers to know when pages change.
|
|
||||||
open func registerForPageChanges() {
|
|
||||||
MVMCoreNavigationHandler.shared()?.addDelegate(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func registerForFocusChanges() {
|
private func registerForFocusChanges() {
|
||||||
//Since foucs shifted to other elements cancelling existing focus shift notifications if any
|
//Since foucs shifted to other elements cancelling existing focus shift notifications if any
|
||||||
NotificationCenter.default.publisher(for: UIAccessibility.elementFocusedNotification)
|
NotificationCenter.default.publisher(for: UIAccessibility.elementFocusedNotification)
|
||||||
.sink { [weak self] notification in
|
.sink { [weak self] _ in
|
||||||
self?.cancelAllOperations()
|
self?.cancelAllOperations()
|
||||||
}.store(in: &anyCancellable)
|
}.store(in: &anyCancellable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func registerForTopNotificationsChanges() {
|
func registerForTopNotificationsChanges() {
|
||||||
NotificationHandler.shared()?.onNotificationWillShow
|
NotificationHandler.shared()?.onNotificationWillShow
|
||||||
.sink { [weak self] (_, model) in
|
.sink { [weak self] (_, model) in
|
||||||
self?.hasTopNotitificationInPage = true
|
self?.hasTopNotitificationInPage = true
|
||||||
@ -150,6 +113,49 @@ open class AccessibilityHandler {
|
|||||||
}.store(in: &anyCancellable)
|
}.store(in: &anyCancellable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Registers to know when pages change.
|
||||||
|
open func registerForPageChanges() {
|
||||||
|
NavigationHandler.shared()
|
||||||
|
.onNavigation
|
||||||
|
.sink { [self] (event, operation) in
|
||||||
|
switch event {
|
||||||
|
case .willNavigate:
|
||||||
|
willNavigate(operation)
|
||||||
|
@unknown default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}.store(in: &anyCancellable)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func willNavigate(_ operation: NavigationOperation) {
|
||||||
|
previousAccessiblityElement = nil
|
||||||
|
if let announcementText {
|
||||||
|
post(notification: .announcement, argument: announcementText)
|
||||||
|
}
|
||||||
|
if let subNavManagerController = operation.toNavigationControllerViewControllers?.last as? SubNavManagerController {
|
||||||
|
delegate = subNavManagerController.getCurrentViewController() as? MVMCoreViewControllerProtocol
|
||||||
|
} else {
|
||||||
|
delegate = operation.toNavigationControllerViewControllers?.last as? MVMCoreViewControllerProtocol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*private func didNavigate(_ operation: NavigationOperation) {
|
||||||
|
guard UIAccessibility.isVoiceOverRunning,
|
||||||
|
let viewController = operation.toNavigationControllerViewControllers?.last,
|
||||||
|
canPostAccessbilityNotification(for: viewController) else { return }
|
||||||
|
delegate = viewController as? MVMCoreViewControllerProtocol
|
||||||
|
guard let view = operation.toNavigationControllerViewControllers?.last?.view else { return }
|
||||||
|
view.accessibilityElements = getAccessibilityElementsOnScreen()
|
||||||
|
if hasTopNotitificationInPage {
|
||||||
|
previousAccessiblityElement = getFirstFocusedElementOnScreen()
|
||||||
|
} else {
|
||||||
|
let accessbilityElement = getUIElementBasedOn(id: accessibilityId)
|
||||||
|
post(notification: .screenChanged, argument: accessbilityElement ?? getFirstFocusedElementOnScreen())
|
||||||
|
accessibilityId = nil
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// MARK: - Accessibility Handler operation events
|
||||||
open func capturePreviousFocusElement() {
|
open func capturePreviousFocusElement() {
|
||||||
previousAccessiblityElement = UIAccessibility.focusedElement(using: .notificationVoiceOver)
|
previousAccessiblityElement = UIAccessibility.focusedElement(using: .notificationVoiceOver)
|
||||||
}
|
}
|
||||||
@ -166,57 +172,27 @@ open class AccessibilityHandler {
|
|||||||
accessibilityOperationQueue.cancelAllOperations()
|
accessibilityOperationQueue.cancelAllOperations()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func post(notification type: AccessibilityNotificationType, argument: Any? = nil) {
|
public func post(notification type: UIAccessibility.Notification, argument: Any? = nil) {
|
||||||
guard UIAccessibility.isVoiceOverRunning else { return }
|
guard UIAccessibility.isVoiceOverRunning else { return }
|
||||||
let accessbilityOperation = AccessbilityOperation(notificationType: type, argument: argument)
|
let accessbilityOperation = AccessbilityOperation(notificationType: type, argument: argument)
|
||||||
add(operation: accessbilityOperation)
|
add(operation: accessbilityOperation)
|
||||||
}
|
}
|
||||||
|
|
||||||
//To get first foucs element on the screen
|
//To get first focus element on the screen
|
||||||
open func getFirstFocusedElementOnScreen() -> Any? {
|
open func getFirstFocusedElementOnScreen() -> Any? {
|
||||||
(delegate as? UIViewController)?.navigationItem.leftBarButtonItem ?? (delegate as? UIViewController)?.navigationItem.titleView ?? (delegate as? UIViewController)?.navigationController?.navigationBar
|
(delegate as? UIViewController)?.navigationItem.leftBarButtonItem ?? (delegate as? UIViewController)?.navigationItem.titleView ?? (delegate as? UIViewController)?.navigationController?.navigationBar
|
||||||
}
|
}
|
||||||
|
|
||||||
//Subclass can decide to trigger Accessibility notification on screen change.
|
//Subclass can decide to trigger Accessibility notification on screen change.
|
||||||
open func canPostAccessbilityNotification(for viewController: UIViewController) -> Bool { true }
|
open func canPostAccessbilityNotification(for viewController: UIViewController) -> Bool { true }
|
||||||
}
|
|
||||||
|
|
||||||
extension AccessibilityHandler: MVMCorePresentationDelegateProtocol {
|
|
||||||
|
|
||||||
open func navigationController(_ navigationController: UINavigationController, prepareDisplayFor viewController: UIViewController) {
|
func getPreDefinedFocusedElementIfAny() -> UIView? {
|
||||||
previousAccessiblityElement = nil
|
guard let accessibilityId, let models: [any Identifiable] = (delegate?.delegateObject?() as? MVMCoreUIDelegateObject)?.moleculeDelegate?.getRootMolecules().allMoleculesOfType() else { return nil }
|
||||||
delegate = viewController as? MVMCoreViewControllerProtocol
|
guard !models.isEmpty,
|
||||||
if let announcementText {
|
let model = (models.filter { ($0.id as? String) == accessibilityId }).first else { return nil }
|
||||||
post(notification: .announcement, argument: announcementText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) {
|
|
||||||
guard UIAccessibility.isVoiceOverRunning,
|
|
||||||
canPostAccessbilityNotification(for: viewController) else { return }
|
|
||||||
//TODO: - For Tabbar change: adding 1.5 sec delay to shift focus to the top. for Temp fix added to check on childern count. If we have top notification in page on pageLoad, we have postnotification for shifting the focus so in this case we are not posting accessiblity notifcation
|
|
||||||
if hasTopNotitificationInPage {
|
|
||||||
previousAccessiblityElement = getFirstFocusedElementOnScreen()
|
|
||||||
} else {
|
|
||||||
let accessbilityElement = getAccessbilityFocusedElement()
|
|
||||||
let operationType: NavigationOperationType = navigationController.children.count == 1 ? .tab : .default //TODO: - need to identify the operationType
|
|
||||||
post(notification: operationType == .tab ? .controllerChanged : .layoutChanged, argument: accessbilityElement ?? getFirstFocusedElementOnScreen())
|
|
||||||
accessibilityId = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AccessibilityHandler {
|
|
||||||
|
|
||||||
private func getAccessbilityFocusedElement() -> UIView? {
|
|
||||||
guard let accessibilityModels: [any AccessibilityElementProtocol] = (delegate?.delegateObject?() as? MVMCoreUIDelegateObject)?.moleculeDelegate?.getRootMolecules().allMoleculesOfType() else { return nil }
|
|
||||||
guard !accessibilityModels.isEmpty,
|
|
||||||
let accessibilityModel = (accessibilityModels.filter { $0.id == accessibilityId }).first as? MoleculeModelProtocol else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return (delegate as? UIViewController)?.view?.getMoleculeViews { (subView: MoleculeViewProtocol) in
|
return (delegate as? UIViewController)?.view?.getMoleculeViews { (subView: MoleculeViewProtocol) in
|
||||||
guard let moleculeModel = (subView as? MoleculeViewModelProtocol)?.getMoleculeModel() as? (any AccessibilityElementProtocol),
|
guard let moleculeModel = (subView as? MoleculeViewModelProtocol)?.moleculeModel as? (any Identifiable),
|
||||||
moleculeModel.id == (accessibilityModel as? (any AccessibilityElementProtocol))?.id else {
|
(moleculeModel.id as? String) == (model.id as? String) else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -224,6 +200,44 @@ extension AccessibilityHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Accessibility Handler Behaviour
|
||||||
|
///Accessibility Handler Behaviour to detect page shown and post notification to first interactive element on screen or the pre-defined focused element.
|
||||||
|
struct AccessibilityHandlerBehaviorModel: PageBehaviorModelProtocol {
|
||||||
|
|
||||||
|
var shouldAllowMultipleInstances = false
|
||||||
|
static var identifier = "accessibilityHandlerBehaviorModel"
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccessibilityHandlerBehavior: PageVisibilityBehavior {
|
||||||
|
|
||||||
|
required public init(model: PageBehaviorModelProtocol, delegateObject: MVMCoreUIDelegateObject?) { }
|
||||||
|
|
||||||
|
public func willShowPage(_ delegateObject: MVMCoreUIDelegateObject?) {
|
||||||
|
let viewController = updateAccessibilityViews(delegateObject)
|
||||||
|
AccessibilityHandler.shared()?.post(notification: .layoutChanged, argument: AccessibilityHandler.shared()?.getPreDefinedFocusedElementIfAny() ?? AccessibilityHandler.shared()?.getFirstFocusedElementOnScreen())
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateAccessibilityViews(_ delegateObject: MVMCoreUIDelegateObject?) -> UIViewController? {
|
||||||
|
var accessibilityElements: [Any?] = [MVMCoreUISplitViewController.main()?.topAlertView, MVMCoreUISplitViewController.main()?.navigationController]
|
||||||
|
var viewController: UIViewController?
|
||||||
|
if let managerController = (delegateObject?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController {
|
||||||
|
var managerControllerViews = [Any?]()
|
||||||
|
managerControllerViews.append(managerController.navigationController)
|
||||||
|
managerControllerViews.append(managerController.tabs)
|
||||||
|
managerControllerViews.append(contentsOf: managerController.view.subviews)
|
||||||
|
managerController.view.accessibilityElements = managerControllerViews.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
|
||||||
|
viewController = controller
|
||||||
|
}
|
||||||
|
return viewController
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
extension UIView {
|
extension UIView {
|
||||||
|
|
||||||
private func getNestedSubviews<T>() -> [T] {
|
private func getNestedSubviews<T>() -> [T] {
|
||||||
|
|||||||
@ -372,7 +372,6 @@ public typealias ActionBlockConfirmation = () -> (Bool)
|
|||||||
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||||
super.set(with: model, delegateObject, additionalData)
|
super.set(with: model, delegateObject, additionalData)
|
||||||
self.delegateObject = delegateObject
|
self.delegateObject = delegateObject
|
||||||
self.model = model
|
|
||||||
|
|
||||||
guard let model = model as? ToggleModel else { return }
|
guard let model = model as? ToggleModel else { return }
|
||||||
|
|
||||||
@ -422,5 +421,5 @@ extension Toggle {
|
|||||||
|
|
||||||
extension Toggle: MoleculeViewModelProtocol {
|
extension Toggle: MoleculeViewModelProtocol {
|
||||||
|
|
||||||
public func getMoleculeModel() -> MoleculeModelProtocol? { model }
|
public var moleculeModel: MoleculeModelProtocol? { model }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, AccessibilityElementProtocol {
|
public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, Identifiable {
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -32,7 +32,6 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, Accessibilit
|
|||||||
public var fieldKey: String?
|
public var fieldKey: String?
|
||||||
public var groupName: String = FormValidator.defaultGroupName
|
public var groupName: String = FormValidator.defaultGroupName
|
||||||
public var baseValue: AnyHashable?
|
public var baseValue: AnyHashable?
|
||||||
public var id: String
|
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
// MARK: - Keys
|
// MARK: - Keys
|
||||||
@ -56,7 +55,6 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, Accessibilit
|
|||||||
case offKnobTintColor
|
case offKnobTintColor
|
||||||
case fieldKey
|
case fieldKey
|
||||||
case groupName
|
case groupName
|
||||||
case id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
@ -132,7 +130,6 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, Accessibilit
|
|||||||
}
|
}
|
||||||
enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
|
enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
|
||||||
readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false
|
readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false
|
||||||
id = try typeContainer.decode(forKey: .id, default: { UUID().uuidString }())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
|||||||
@ -104,5 +104,12 @@ public extension ModelRegistry {
|
|||||||
|
|
||||||
public protocol MoleculeViewModelProtocol: UIView {
|
public protocol MoleculeViewModelProtocol: UIView {
|
||||||
|
|
||||||
func getMoleculeModel() -> MoleculeModelProtocol?
|
var moleculeModel: MoleculeModelProtocol? { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension MoleculeViewModelProtocol {
|
||||||
|
|
||||||
|
var moleculeModel: MoleculeModelProtocol? {
|
||||||
|
get { nil }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,8 +12,3 @@ import Foundation
|
|||||||
/// Should return the argument to use for posting a layout change.
|
/// Should return the argument to use for posting a layout change.
|
||||||
func getAccessibilityLayoutChangedArgument() -> Any?
|
func getAccessibilityLayoutChangedArgument() -> Any?
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol AccessibilityElementProtocol: Identifiable {
|
|
||||||
|
|
||||||
var id: String { get set }
|
|
||||||
}
|
|
||||||
|
|||||||
@ -11,7 +11,11 @@ import MVMCore
|
|||||||
|
|
||||||
@objcMembers open class CoreUIObject: MVMCoreObject {
|
@objcMembers open class CoreUIObject: MVMCoreObject {
|
||||||
public var alertHandler: AlertHandler?
|
public var alertHandler: AlertHandler?
|
||||||
public var topNotificationHandler: NotificationHandler?
|
public var topNotificationHandler: NotificationHandler? {
|
||||||
|
didSet {
|
||||||
|
accessibilityHandler?.registerForTopNotificationsChanges()
|
||||||
|
}
|
||||||
|
}
|
||||||
public var accessibilityHandler: AccessibilityHandler?
|
public var accessibilityHandler: AccessibilityHandler?
|
||||||
|
|
||||||
open override func defaultInitialSetup() {
|
open override func defaultInitialSetup() {
|
||||||
@ -24,5 +28,6 @@ import MVMCore
|
|||||||
viewControllerMapping = MVMCoreUIViewControllerMappingObject()
|
viewControllerMapping = MVMCoreUIViewControllerMappingObject()
|
||||||
loggingDelegate = MVMCoreUILoggingHandler()
|
loggingDelegate = MVMCoreUILoggingHandler()
|
||||||
alertHandler = AlertHandler()
|
alertHandler = AlertHandler()
|
||||||
|
accessibilityHandler = AccessibilityHandler()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
MVMCoreUI/OtherHandlers/MVMCoreUISession+Extension.swift
Normal file
22
MVMCoreUI/OtherHandlers/MVMCoreUISession+Extension.swift
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// MVMCoreUISession.swift
|
||||||
|
// MVMCoreUI
|
||||||
|
//
|
||||||
|
// Created by Bandaru, Krishna Kishore on 16/09/23.
|
||||||
|
// Copyright © 2023 Verizon Wireless. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
@objc public extension MVMCoreUISession {
|
||||||
|
|
||||||
|
@objc func applyGlobalMVMCoreUIBehaviors(to viewController: UIViewController) {
|
||||||
|
|
||||||
|
guard var behaviorController = viewController as? PageBehaviorHandlerProtocol, let delegateObject = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() else { return }
|
||||||
|
|
||||||
|
let accessibilityHandlerBehavior = AccessibilityHandlerBehavior(model: AccessibilityHandlerBehaviorModel(), delegateObject: (delegateObject as! MVMCoreUIDelegateObject))
|
||||||
|
|
||||||
|
let behaviors = behaviorController.behaviors ?? []
|
||||||
|
behaviorController.behaviors = behaviors + [accessibilityHandlerBehavior]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@
|
|||||||
#import "MVMCoreUISession.h"
|
#import "MVMCoreUISession.h"
|
||||||
#import "MFLoadingViewController.h"
|
#import "MFLoadingViewController.h"
|
||||||
#import "NSLayoutConstraint+MFConvenience.h"
|
#import "NSLayoutConstraint+MFConvenience.h"
|
||||||
|
#import <MVMCoreUI/MVMCoreUI-Swift.h>
|
||||||
@import MVMCore.MVMCoreObject;
|
@import MVMCore.MVMCoreObject;
|
||||||
|
|
||||||
@interface MVMCoreUISession () <MVMCoreLoadingOverlayDelegateProtocol>
|
@interface MVMCoreUISession () <MVMCoreLoadingOverlayDelegateProtocol>
|
||||||
@ -60,6 +61,7 @@
|
|||||||
|
|
||||||
- (void)applyGlobalBehaviorsToController:(nonnull UIViewController *)viewController {
|
- (void)applyGlobalBehaviorsToController:(nonnull UIViewController *)viewController {
|
||||||
// Allow extending frameworks to apply behaviors to add cross cutting concerns to the base controllers.
|
// Allow extending frameworks to apply behaviors to add cross cutting concerns to the base controllers.
|
||||||
|
[self applyGlobalMVMCoreUIBehaviorsTo:viewController];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user