Added RotorHandler & addressed review comments

This commit is contained in:
Krishna Kishore Bandaru 2023-10-13 22:28:00 +05:30
parent 57d83b4884
commit 521eaa7c15
8 changed files with 283 additions and 157 deletions

View File

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 52;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@ -169,6 +169,7 @@
52B201D224081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D024081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethod.swift */; };
52B201D324081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B201D124081CFB00D2011E /* ListLeftVariableRadioButtonAndPaymentMethodModel.swift */; };
7199C8162A4F3A64001568B7 /* AccessibilityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */; };
71BE969E2AD96BE6000B5DB7 /* RotorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BE969D2AD96BE6000B5DB7 /* RotorHandler.swift */; };
8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BAF241B56530099AC56 /* ListRightVariableTotalDataModel.swift */; };
8D070BB2241B56AD0099AC56 /* ListRightVariableTotalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D070BB1241B56AD0099AC56 /* ListRightVariableTotalData.swift */; };
8D084AD02410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D084ACF2410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift */; };
@ -756,6 +757,7 @@
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>"; };
7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityHandler.swift; sourceTree = "<group>"; };
71BE969D2AD96BE6000B5DB7 /* RotorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RotorHandler.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>"; };
8D084ACF2410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOneColumnFullWidthTextBodyTextModel.swift; sourceTree = "<group>"; };
@ -1457,6 +1459,7 @@
isa = PBXGroup;
children = (
7199C8152A4F3A64001568B7 /* AccessibilityHandler.swift */,
71BE969D2AD96BE6000B5DB7 /* RotorHandler.swift */,
);
path = Accessibility;
sourceTree = "<group>";
@ -3012,6 +3015,7 @@
D29C559325C0992D0082E7D6 /* VideoModel.swift in Sources */,
D264FAA5243F66A500D98315 /* CollectionTemplateItemProtocol.swift in Sources */,
D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */,
71BE969E2AD96BE6000B5DB7 /* RotorHandler.swift in Sources */,
AA71AD3E24A32FCE00ACA76F /* HeadersH2LinkModel.swift in Sources */,
8DD1E36E243B3CFB00D8F2DF /* ListThreeColumnInternationalDataModel.swift in Sources */,
D243859923A16B1800332775 /* Container.swift in Sources */,

View File

@ -49,49 +49,24 @@ public class AccessbilityOperation: MVMCoreOperation {
}
open class AccessibilityHandler {
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 static func shared() -> Self? {
guard let shared = CoreUIObject.sharedInstance()?.accessibilityHandler else { return nil }
return MVMCoreActionUtility.fatalClassCheck(object: shared)
}
public var accessibilityId: String?
public var previousAccessiblityElement: Any?
public var anyCancellable: Set<AnyCancellable> = []
public weak var delegate: MVMCoreViewControllerProtocol?
private var rotorIndexes: [RotorType: Int] = [:]
private var hasTopNotificationInPage: Bool { NotificationHandler.shared()?.isNotificationShowing() ?? false }
public weak var delegate: MVMCoreViewControllerProtocol? { delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol }
public weak var delegateObject: MVMCoreUIDelegateObject?
private var hasTopNotificationInPage: Bool { delegate?.loadObject??.responseJSON?.optionalDictionaryForKey("TopNotification") != nil || delegate?.loadObject??.responseInfoMap?.optionalStringForKey("userMessage") != nil }
private let accessibilityOperationQueue: OperationQueue = {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
return queue
}()
lazy var rotorHandler = RotorHandler(accessibilityHandler: self)
public init() {
registerForFocusChanges()
@ -129,17 +104,22 @@ open class AccessibilityHandler {
}
}
/**
When we push a new viewcontroller on to a Navigation stack from iOS 13+ Accessibility voiceover is going to the element inside of the viewcontroller. Not treating navigationController left/back bar button as first element. So alternatively we are setting accessibility elements in viewWillAppear untill viewDidAppear then we are resetting back So that there will not be accessibility order issue.
https://developer.apple.com/forums/thread/655359
https://developer.apple.com/forums/thread/675427
*/
extension AccessibilityHandler {
public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) {
rotorIndexes = [:]
previousAccessiblityElement = nil
rotorHandler.onPageNew(rootMolecules: rootMolecules, delegateObject)
guard let loadObject = (delegateObject?.loadDelegate as? MVMCoreViewControllerProtocol)?.loadObject else { return }
accessibilityId = loadObject?.pageJSON?.optionalStringForKey("accessibilityId")
if let announcementText = loadObject?.pageJSON?.optionalStringForKey("announcementText") {
post(notification: .announcement, argument: announcementText)
}
delegate = delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol
self.delegateObject = delegateObject
}
//MARK: - PageVisibiltyBehaviour
@ -158,10 +138,12 @@ extension AccessibilityHandler {
///We need to shift focus to any element mentioned in server response i.e to retain focus of the element in new page, from where action is triggered.
///https://oneconfluence.verizon.com/display/MFD/Accessibility+-+Focus+Retain
public func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) {
identifyAndPrepareRotors(delegateObject)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
(delegateObject?.moleculeDelegate as? UIViewController)?.view.accessibilityElements = nil
defer {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
(delegateObject?.moleculeDelegate as? UIViewController)?.view.accessibilityElements = nil
}
}
rotorHandler.onPageShown(delegateObject)
guard let accessibilityElement = getPreDefinedFocusedElementIfAny() else { return }
accessibilityId = nil
if hasTopNotificationInPage {
@ -173,115 +155,19 @@ extension AccessibilityHandler {
//MARK: - Accessibility Methods
private func updateAccessibilityViews(_ delegateObject: MVMCoreUIDelegateObject?) {
var currentController = delegateObject?.moleculeDelegate as? UIViewController
guard var currentController = delegateObject?.moleculeDelegate as? UIViewController,
currentController.navigationController != nil else { return }///If no navigationController, we can go with the default behaviour of Accessibility voiceover.
var accessibilityElements: [Any?] = [MVMCoreUISplitViewController.main()?.topAlertView, MVMCoreUISplitViewController.main()?.navigationController]
if let manager = ((delegateObject?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? MVMCoreViewManagerProtocol & UIViewController),
let managerAccessibilityElements = manager.getAccessibilityElements() {
let managerAccessibilityElements = manager.getAccessibilityElements() {
accessibilityElements.append(contentsOf: managerAccessibilityElements)
accessibilityElements.append(contentsOf: manager.view.subviews)
currentController = manager
} else {
accessibilityElements.append(contentsOf: currentController?.view.subviews ?? [])
accessibilityElements.append(contentsOf: currentController.view.subviews)
}
accessibilityElements.append(MVMCoreUISplitViewController.main()?.tabBar)
currentController?.view.accessibilityElements = accessibilityElements.compactMap { $0 }
}
//MARK: - Rotor Methods
private func identifyAndPrepareRotors(_ delegateObject: MVMCoreUIDelegateObject?) {
var rotorElements: [UIAccessibilityCustomRotor] = []
let currentViewController = ((delegateObject?.moleculeDelegate as? MVMCoreViewManagerViewControllerProtocol)?.manager as? SubNavManagerController) ?? (delegateObject?.moleculeDelegate as? ViewController)
for type in RotorType.allCases {
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?] = []
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 {
$0.accessibilityTraits.contains(type.trait)
})
}
if let rotorElements = getRotorElementsFrom(template: template, type: type) {
accessibilityElements.append(contentsOf: rotorElements)
}
if let tabBarHidden = (template as? TabPageModelProtocol)?.tabBarHidden, !tabBarHidden {
accessibilityElements.append(contentsOf: (MVMCoreUISplitViewController.main()?.tabBar?.subviews ?? []).filter {
$0.accessibilityTraits.contains(type.trait)
})
}
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
var rotorElements: [(model: MoleculeModelProtocol, indexPath: IndexPath)] = [] ///Identifying the trait mapped elements models
var currentIndexPath: IndexPath?
rotorElements = templateModel.reduceDepthFirstTraverse(options: .parentFirst, depth: 0, initialResult: rotorElements, nextPartialResult: { result, model, depth in
if let listModel = model as? (ListItemModelProtocol & MoleculeModelProtocol), let indexPath = currentViewController.getIndexPath(for: listModel) {
currentIndexPath = indexPath
}
var result = result
if (model.accessibilityTraits?.contains(type.trait) ?? false), let currentIndexPath {
result.append((model, currentIndexPath))
}
return result
})
let headerViewElements = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [currentViewController.tableView.tableHeaderView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any]
let footerViewElements = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [currentViewController.tableView.tableFooterView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any]
let otherInteractiveElements = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [currentViewController.view].compactMap { $0 }, excludedViews: [tableView, tableView.tableFooterView, tableView.tableHeaderView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any]
return headerViewElements + otherInteractiveElements + (rotorElements as [Any]) + footerViewElements
} else if let currentViewController = template as? MoleculeStackTemplate { //Stack templates
return MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [currentViewController.view].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any]
}
return nil
}
private func createRotor(_ elements: [Any], for type: RotorType) -> UIAccessibilityCustomRotor? {
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 {
rotorIndex += 1
if rotorIndex > elements.count {
rotorIndex = 1
}
} else {
rotorIndex -= 1
if rotorIndex <= 0 {
rotorIndex = elements.count
}
}
var rotorElement = elements[rotorIndex - 1]
if let tableView = (self.delegate as? MoleculeListTemplate)?.tableView,
let element = rotorElement as? (model: MoleculeModelProtocol, indexPath: IndexPath) { //for List templates
tableView.scrollToRow(at: element.indexPath, at: .middle, animated: false)
rotorElement = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [tableView.cellForRow(at: element.indexPath)].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) && $0.model?.id == element.model.id }.first as Any
}
self.rotorIndexes[type] = rotorIndex
post(notification: .layoutChanged, argument: rotorElement)
return UIAccessibilityCustomRotorItemResult(targetElement: rotorElement as! NSObjectProtocol, targetRange: nil)
}
currentController.view.accessibilityElements = accessibilityElements.compactMap { $0 }
}
}
@ -309,7 +195,7 @@ extension AccessibilityHandler {
}.store(in: &anyCancellable)
NotificationHandler.shared()?.onNotificationWillDismiss
.sink { [weak self] (view, model) in
self?.post(notification: .announcement, argument: MVMCoreUIUtility.hardcodedString(withKey: "AccTopAlertClosed"), priority: .veryHigh)
self?.post(notification: .announcement, argument: MVMCoreUIUtility.hardcodedString(withKey: "AccTopAlertClosed"))
}.store(in: &anyCancellable)
NotificationHandler.shared()?.onNotificationDismissed
.sink { [weak self] (view, model) in
@ -333,7 +219,7 @@ open class AccessibilityHandlerBehavior: PageVisibilityBehavior, PageMoleculeTra
}
//MARK: - PageMoleculeTransformationBehavior
public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) {
open func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) {
accessibilityHandler?.onPageNew(rootMolecules: rootMolecules, delegateObject)
}

View File

@ -0,0 +1,228 @@
//
// RotorHandler.swift
// MVMCoreUI
//
// Created by Bandaru, Krishna Kishore on 13/10/23.
// Copyright © 2023 Verizon Wireless. All rights reserved.
//
import Foundation
import Combine
fileprivate 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 protocol RotorViewElementsProtocol: UIViewController {
var topView: UIView? { get set }
var middleView: UIView? { get set }
var bottomView: UIView? { get set }
}
public protocol RotorScrollDelegateProtocol: MVMCoreViewControllerProtocol {
func scrollRectToVisible(_ rect: CGRect, animated: Bool)
}
public protocol RotorListTypeDelegateProtocol: RotorScrollDelegateProtocol {
func scrollToRow(at indexPath: IndexPath, at scrollPosition: UITableView.ScrollPosition, animated: Bool) -> Void
func cellForRow(at indexPath: IndexPath) -> UIView?
}
struct RotorElement {
let indexPath: IndexPath
let model: MoleculeModelProtocol
}
class RotorHandler {
private var rotorIndexes: [RotorType: Int] = [:]
private var rotorElements: [RotorType: [Any]] = [:]
private var anyCancellable: Set<AnyCancellable> = []
private weak var delegateObject: MVMCoreUIDelegateObject?
private weak var delegate: MVMCoreViewControllerProtocol? { delegateObject?.moleculeDelegate as? MVMCoreViewControllerProtocol }
private weak var accessibilityHandler: AccessibilityHandler?
init(accessibilityHandler: AccessibilityHandler?) {
self.accessibilityHandler = accessibilityHandler
registerForVoiceOverChanges()
}
private func registerForVoiceOverChanges() {
NotificationCenter.default.publisher(for: UIAccessibility.voiceOverStatusDidChangeNotification)
.sink { [weak self] _ in
guard UIAccessibility.isVoiceOverRunning, (self?.rotorElements.isEmpty ?? true) else { return }
self?.identifyAndPrepareRotors(self?.delegateObject)
}.store(in: &anyCancellable)
}
public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) {
rotorIndexes = [:]
rotorElements = [:]
self.delegateObject = delegateObject
}
public func onPageShown(_ delegateObject: MVMCoreUIDelegateObject?) {
identifyAndPrepareRotors(delegateObject)
}
private func identifyAndPrepareRotors(_ delegateObject: MVMCoreUIDelegateObject?) {
guard UIAccessibility.isVoiceOverRunning,
let currentViewController = (delegateObject?.moleculeDelegate as? (MVMCoreViewControllerProtocol & UIViewController)) else { return }
var customRotors: [UIAccessibilityCustomRotor] = []
for type in RotorType.allCases {
if let elements = getTraitMappedElements(currentViewController, type: type),
!elements.isEmpty,
let rotor = createRotor(for: type) {
rotorElements[type] = elements
customRotors.append(rotor)
}
}
currentViewController.accessibilityCustomRotors = customRotors
}
private func getTraitMappedElements(_ template: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? {
var accessibilityElements: [Any?] = []
if let manager = (template as? MVMCoreViewManagerViewControllerProtocol)?.manager as? (MVMCoreViewControllerProtocol & UIViewController) {
accessibilityElements.append(contentsOf: getRotorElementsFrom(manager: manager, type: type) ?? [])
}
if let rotorElements = getRotorElementsFrom(template: template, type: type) {
accessibilityElements.append(contentsOf: rotorElements)
}
if let tabBarHidden = (template as? TabPageModelProtocol)?.tabBarHidden, !tabBarHidden {
accessibilityElements.append(contentsOf: (MVMCoreUISplitViewController.main()?.tabBar?.subviews ?? []).filter {
$0.accessibilityTraits.contains(type.trait)
})
}
return accessibilityElements.compactMap { $0 }
}
private func getRotorElementsFrom(manager: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? {
guard let elements = (manager as? MVMCoreViewManagerProtocol)?.getAccessibilityElements() as? [UIView] else { return nil }
return MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: elements).filter { $0.accessibilityTraits.contains(type.trait) } as [Any]
}
private func getRotorElementsFrom(template: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? {
guard let template = template as? (RotorViewElementsProtocol & MVMCoreViewControllerProtocol & UIViewController) else {
return MVMCoreUIUtility.findViews(by: UIView.self, views: [template?.view].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any] //BAU Pages
}
let topViewRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.topView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any]
var reusableViewRotorElements: [Any] = []
if let middleView = template.middleView {
if middleView.isKind(of: UITableView.self),
let template = template as? (any MoleculeListProtocol & ViewController) {
reusableViewRotorElements = getRotorElements(from: template, type: type) ?? []
} else {
reusableViewRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [middleView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any]
}
}
let bottomViewRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.bottomView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any]
let remainingRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.view], excludedViews: [template.topView, template.middleView, template.middleView, template.bottomView].compactMap { $0 }).filter { $0.accessibilityTraits.contains(type.trait) } as [Any]
return topViewRotorElements + reusableViewRotorElements + remainingRotorElements + bottomViewRotorElements
}
private func getRotorElements(from template: (any MoleculeListProtocol & ViewController)?, type: RotorType) -> [Any]? {
guard let templateModel = template?.model,
let moleculeList = template else { return nil }
var rotorElements: [RotorElement] = []
var traitIndexPath: IndexPath?
rotorElements = templateModel.reduceDepthFirstTraverse(options: .parentFirst, depth: 0, initialResult: rotorElements) { result, model, depth in
if let listModel = model as? (ListItemModelProtocol & MoleculeModelProtocol), let indexPath = moleculeList.getIndexPath(for: listModel) {
traitIndexPath = indexPath
}
var result = result
if (model.accessibilityTraits?.contains(type.trait) ?? false), let traitIndexPath {
result.append(.init(indexPath: traitIndexPath, model: model))
}
return result
}
return rotorElements
}
private func createRotor(for type: RotorType) -> UIAccessibilityCustomRotor? {
return type.getUIAccessibilityCustomRotor { [weak self] predicate in
guard let self, let elements = self.rotorElements[type] else { return UIAccessibilityCustomRotorItemResult() }
var rotorIndex = self.rotorIndexes[type] ?? 0
if predicate.searchDirection == .next {
rotorIndex += 1
if rotorIndex > elements.count {
rotorIndex = 1
}
} else {
rotorIndex -= 1
if rotorIndex <= 0 {
rotorIndex = elements.count
}
}
var rotorElement = elements[rotorIndex - 1]
if let element = rotorElement as? RotorElement,
let controller = self.delegate as? RotorListTypeDelegateProtocol {
controller.scrollToRow(at: element.indexPath, at: .middle, animated: false)
guard let cellView = controller.cellForRow(at: element.indexPath) else { return UIAccessibilityCustomRotorItemResult() }
rotorElement = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [cellView]).filter { $0.accessibilityTraits.contains(type.trait) && $0.model?.id == element.model.id }.first as Any
} else {
if let viewElement = (rotorElement as? UIView) {
let convertedFrame = viewElement.convert(viewElement.frame, to: (self.delegate as? UIViewController)?.view)
(self.delegate as? RotorScrollDelegateProtocol)?.scrollRectToVisible(convertedFrame, animated: false)
}
}
self.rotorIndexes[type] = rotorIndex
self.accessibilityHandler?.post(notification: .layoutChanged, argument: rotorElement)
return UIAccessibilityCustomRotorItemResult(targetElement: rotorElement as! NSObjectProtocol, targetRange: nil)
}
}
}
public extension RotorViewElementsProtocol {
var topView: UIView? { nil }
var middleView: UIView? { nil }
var bottomView: UIView? { nil }
}
extension RotorScrollDelegateProtocol {
public func scrollRectToVisible(_ rect: CGRect, animated: Bool) { }
}
extension RotorListTypeDelegateProtocol {
func scrollToRow(at indexPath: IndexPath, at scrollPosition: UITableView.ScrollPosition, animated: Bool) { }
func cellForRow(at indexPath: IndexPath) -> UIView? { nil }
}
extension ThreeLayerTableViewController: RotorListTypeDelegateProtocol {
public func scrollToRow(at indexPath: IndexPath, at scrollPosition: UITableView.ScrollPosition, animated: Bool) {
tableView.scrollToRow(at: indexPath, at: scrollPosition, animated: animated)
}
public func cellForRow(at indexPath: IndexPath) -> UIView? {
tableView.cellForRow(at: indexPath)
}
}
extension ThreeLayerViewController: RotorScrollDelegateProtocol {
public func scrollRectToVisible(_ rect: CGRect, animated: Bool) {
scrollView?.scrollRectToVisible(rect, animated: animated)
}
}

View File

@ -73,7 +73,7 @@ public class AlertOperation: MVMCoreOperation {
if await !self.properties.getIsDisplayed() {
self.markAsFinished()
} else {
(CoreUIObject.sharedInstance()?.loggingDelegate as? MVMCoreUILoggingDelegateProtocol)?.logAlert(with: self.alertObject)
(MVMCoreObject.sharedInstance()?.loggingDelegate as? MVMCoreUILoggingDelegateProtocol)?.logAlert(with: self.alertObject)
if self.isCancelled {
await self.dismissAlertView()
}

View File

@ -294,8 +294,8 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
let classMoleculeB = moleculeB as? NSObjectProtocol {
return classMoleculeA === classMoleculeB
}
// Do json check
return moleculeA.toJSON() == moleculeB.toJSON()
// ID check
return moleculeA.id == moleculeB.id
}
}

View File

@ -8,15 +8,20 @@
import UIKit
open class ThreeLayerTableViewController: ProgrammaticTableViewController {
open class ThreeLayerTableViewController: ProgrammaticTableViewController, RotorViewElementsProtocol {
//--------------------------------------------------
// MARK: - Main Views
//--------------------------------------------------
private var topView: UIView?
private var bottomView: UIView?
private var headerView: UIView?
private var footerView: UIView?
public var topView: UIView?
public var middleView: UIView? {
get { tableView }
set { }
}
public var bottomView: UIView?
//--------------------------------------------------
// MARK: - Properties

View File

@ -9,7 +9,12 @@
import UIKit
import MVMCore
@objcMembers open class CoreUIObject: MVMCoreObject {
@objcMembers
public class CoreUIObject: NSObject {
private static var singleton = CoreUIObject()
public static func sharedInstance() -> CoreUIObject? { singleton }
private override init() {}
public var alertHandler: AlertHandler?
public var topNotificationHandler: NotificationHandler? {
didSet {
@ -17,16 +22,14 @@ import MVMCore
}
}
public var accessibilityHandler: AccessibilityHandler?
open override func defaultInitialSetup() {
public func defaultInitialSetup() {
MVMCoreObject.sharedInstance()?.defaultInitialSetup()
CoreUIModelMapping.registerObjects()
loadHandler = MVMCoreLoadHandler()
cache = MVMCoreCache()
session = MVMCoreUISession()
sessionHandler = MVMCoreSessionTimeHandler()
actionHandler = MVMCoreUIActionHandler()
viewControllerMapping = MVMCoreUIViewControllerMappingObject()
loggingDelegate = MVMCoreUILoggingHandler()
MVMCoreObject.sharedInstance()?.session = MVMCoreUISession()
MVMCoreObject.sharedInstance()?.actionHandler = MVMCoreUIActionHandler()
MVMCoreObject.sharedInstance()?.viewControllerMapping = MVMCoreUIViewControllerMappingObject()
MVMCoreObject.sharedInstance()?.loggingDelegate = MVMCoreUILoggingHandler()
alertHandler = AlertHandler()
accessibilityHandler = AccessibilityHandler()
}

View File

@ -9,8 +9,8 @@
#import "MVMCoreUISession.h"
#import "MFLoadingViewController.h"
#import "NSLayoutConstraint+MFConvenience.h"
#import <MVMCoreUI/MVMCoreUI-Swift.h>
@import MVMCore.MVMCoreObject;
@import MVMCore.MVMCoreLoadingOverlayDelegateProtocol;
@import MVMCore.Swift;
@interface MVMCoreUISession () <MVMCoreLoadingOverlayDelegateProtocol>