|
|
|
|
@ -67,13 +67,22 @@ public protocol RotorScrollDelegateProtocol: MVMCoreViewControllerProtocol {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public protocol RotorListTypeDelegateProtocol: RotorScrollDelegateProtocol {
|
|
|
|
|
func scrollToRow(at indexPath: IndexPath, at scrollPosition: UITableView.ScrollPosition, animated: Bool) -> Void
|
|
|
|
|
func scrollToRow(at indexPath: IndexPath, animated: Bool) -> Void
|
|
|
|
|
func cellForRow(at indexPath: IndexPath) -> UIView?
|
|
|
|
|
func getIndexPathFor(molecule: MoleculeModelProtocol) -> IndexPath?
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct RotorElement {
|
|
|
|
|
let indexPath: IndexPath
|
|
|
|
|
|
|
|
|
|
let indexPath: IndexPath?
|
|
|
|
|
let model: MoleculeModelProtocol
|
|
|
|
|
let carouselItemModel: MoleculeModelProtocol?
|
|
|
|
|
|
|
|
|
|
init(indexPath: IndexPath? = nil, model: MoleculeModelProtocol, carouselItemModel: MoleculeModelProtocol? = nil) {
|
|
|
|
|
self.indexPath = indexPath
|
|
|
|
|
self.model = model
|
|
|
|
|
self.carouselItemModel = carouselItemModel
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class RotorHandler {
|
|
|
|
|
@ -94,7 +103,9 @@ class RotorHandler {
|
|
|
|
|
NotificationCenter.default.publisher(for: UIAccessibility.voiceOverStatusDidChangeNotification)
|
|
|
|
|
.sink { [weak self] _ in
|
|
|
|
|
guard UIAccessibility.isVoiceOverRunning, (self?.rotorElements.isEmpty ?? true) else { return }
|
|
|
|
|
self?.identifyAndPrepareRotors(self?.delegateObject)
|
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
|
|
|
self?.identifyAndPrepareRotors(self?.delegateObject)
|
|
|
|
|
}
|
|
|
|
|
}.store(in: &anyCancellable)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -120,7 +131,7 @@ class RotorHandler {
|
|
|
|
|
customRotors.append(rotor)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
currentViewController.accessibilityCustomRotors = customRotors
|
|
|
|
|
currentViewController.view.accessibilityCustomRotors = customRotors
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func getTraitMappedElements(_ template: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? {
|
|
|
|
|
@ -143,40 +154,50 @@ class RotorHandler {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 { type.filter($0) } as [Any]
|
|
|
|
|
guard let template = template as? (RotorViewElementsProtocol & MVMCoreViewControllerProtocol & ViewController) else {
|
|
|
|
|
return MVMCoreUIUtility.findViews(by: UIView.self, views: [template?.view].compactMap { $0 }).filter { type.filter($0) } as [Any] //BAU pages
|
|
|
|
|
}
|
|
|
|
|
let topViewRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.topView].compactMap { $0 }).filter { type.filter($0) } 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 { type.filter($0) } as [Any]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let bottomViewRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.bottomView].compactMap { $0 }).filter { type.filter($0) } as [Any]
|
|
|
|
|
let remainingRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.view], excludedViews: [template.topView, template.middleView, template.middleView, template.bottomView].compactMap { $0 }).filter { type.filter($0) } as [Any]
|
|
|
|
|
return topViewRotorElements + reusableViewRotorElements + remainingRotorElements + bottomViewRotorElements
|
|
|
|
|
let rotorElements = getRotorElements(from: template, type: type)
|
|
|
|
|
let remainingRotorElements = MVMCoreUIUtility.findViews(by: UIView.self, views: [template.view], excludedViews: [template.topView, template.middleView, template.bottomView].compactMap { $0 }).filter { type.filter($0) } as [Any] //Other elements added to view if any.
|
|
|
|
|
return rotorElements + remainingRotorElements
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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] = []
|
|
|
|
|
private func getRotorElements(from template: (MVMCoreViewControllerProtocol & ViewController & RotorViewElementsProtocol)?, type: RotorType) -> [Any] {
|
|
|
|
|
guard let templateModel = template?.model as? ThreeLayerModelBase else { return [] }
|
|
|
|
|
var rotorElements: [Any] = []
|
|
|
|
|
if let headerView = template?.topView {
|
|
|
|
|
rotorElements.append(contentsOf: MVMCoreUIUtility.findViews(by: UIView.self, views: [headerView].compactMap { $0 }).filter { type.filter($0) })
|
|
|
|
|
}
|
|
|
|
|
let molecules = templateModel.rootMolecules.filter { !($0.id == templateModel.header?.id || $0.id == templateModel.footer?.id) }
|
|
|
|
|
rotorElements.append(contentsOf: getRotorElements(from: molecules, template: template, type: type))
|
|
|
|
|
if let bottomView = template?.bottomView {
|
|
|
|
|
rotorElements.append(contentsOf: MVMCoreUIUtility.findViews(by: UIView.self, views: [bottomView].compactMap { $0 }).filter { type.filter($0) })
|
|
|
|
|
}
|
|
|
|
|
return rotorElements
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func getRotorElements(from molecules: [MoleculeModelProtocol], template: (MVMCoreViewControllerProtocol & ViewController)?, type: RotorType) -> [Any] {
|
|
|
|
|
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) {
|
|
|
|
|
var carouselItemModel: MoleculeModelProtocol?
|
|
|
|
|
var carouselDepth: Int?
|
|
|
|
|
let rotorElements: [RotorElement] = molecules.reduceDepthFirstTraverse(options: .parentFirst, depth: 0, initialResult: []) { result, model, depth in
|
|
|
|
|
if let listModel = model as? (ListItemModelProtocol & MoleculeModelProtocol),
|
|
|
|
|
let indexPath = (template as? RotorListTypeDelegateProtocol)?.getIndexPathFor(molecule: listModel) {
|
|
|
|
|
traitIndexPath = indexPath
|
|
|
|
|
}
|
|
|
|
|
if let _carouselDepth = carouselDepth, depth < _carouselDepth {
|
|
|
|
|
carouselDepth = nil
|
|
|
|
|
carouselItemModel = nil
|
|
|
|
|
}
|
|
|
|
|
if model is CarouselModel { carouselDepth = depth }
|
|
|
|
|
carouselItemModel = model is CarouselItemModelProtocol ? model : carouselItemModel
|
|
|
|
|
var result = result
|
|
|
|
|
if type.modelFilter(model), let traitIndexPath {
|
|
|
|
|
result.append(.init(indexPath: traitIndexPath, model: model))
|
|
|
|
|
if type.modelFilter(model) {
|
|
|
|
|
result.append(.init(indexPath: traitIndexPath, model: model, carouselItemModel: carouselItemModel))
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
return rotorElements
|
|
|
|
|
return rotorElements as [Any]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func createRotor(for type: RotorType) -> UIAccessibilityCustomRotor? {
|
|
|
|
|
@ -195,10 +216,28 @@ class RotorHandler {
|
|
|
|
|
guard rotorIndex >= 0 else { return UIAccessibilityCustomRotorItemResult() }
|
|
|
|
|
var rotorElement = elements[rotorIndex]
|
|
|
|
|
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 { type.filter($0) && $0.model?.id == element.model.id }.first as Any
|
|
|
|
|
let controller = self.delegate as? (RotorViewElementsProtocol & ViewController) {
|
|
|
|
|
var cellView: UIView?
|
|
|
|
|
if let indexPath = element.indexPath, let controller = self.delegate as? (any RotorListTypeDelegateProtocol) {
|
|
|
|
|
controller.scrollToRow(at: indexPath, animated: false)
|
|
|
|
|
cellView = controller.cellForRow(at: indexPath)
|
|
|
|
|
} else {
|
|
|
|
|
cellView = controller.view
|
|
|
|
|
}
|
|
|
|
|
if let cauroselItemModel = element.carouselItemModel {
|
|
|
|
|
guard let carousel = MVMCoreUIUtility.findViews(by: Carousel.self, views: [cellView].compactMap { $0 }).first,
|
|
|
|
|
let index = (carousel.molecules?.firstIndex { $0.id == cauroselItemModel.id }) else { return UIAccessibilityCustomRotorItemResult() }
|
|
|
|
|
let collectionIndexPath = IndexPath(item: index, section: 0)
|
|
|
|
|
carousel.collectionView.scrollToItem(at: collectionIndexPath, at: .left, animated: false)
|
|
|
|
|
let collectionViewCell = carousel.collectionView.cellForItem(at: collectionIndexPath)
|
|
|
|
|
rotorElement = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [collectionViewCell].compactMap { $0 }).filter { type.filter($0) && $0.model?.id == element.model.id }.first as Any
|
|
|
|
|
} else {
|
|
|
|
|
rotorElement = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [cellView].compactMap { $0 }).filter { type.filter($0) && $0.model?.id == element.model.id }.first as Any
|
|
|
|
|
if let viewElement = (rotorElement as? UIView) {
|
|
|
|
|
let convertedFrame = viewElement.convert(viewElement.frame, to: controller.view)
|
|
|
|
|
(self.delegate as? RotorScrollDelegateProtocol)?.scrollRectToVisible(convertedFrame, animated: false)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if let viewElement = (rotorElement as? UIView) {
|
|
|
|
|
let convertedFrame = viewElement.convert(viewElement.frame, to: (self.delegate as? UIViewController)?.view)
|
|
|
|
|
@ -223,19 +262,26 @@ extension RotorScrollDelegateProtocol {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extension RotorListTypeDelegateProtocol {
|
|
|
|
|
func scrollToRow(at indexPath: IndexPath, at scrollPosition: UITableView.ScrollPosition, animated: Bool) { }
|
|
|
|
|
func cellForRow(at indexPath: IndexPath) -> UIView? { nil }
|
|
|
|
|
|
|
|
|
|
public func scrollToRow(at indexPath: IndexPath, animated: Bool) { }
|
|
|
|
|
public func cellForRow(at indexPath: IndexPath) -> UIView? { nil }
|
|
|
|
|
public func getIndexPathFor(molecule: MoleculeModelProtocol) -> IndexPath? { 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 scrollToRow(at indexPath: IndexPath, animated: Bool) {
|
|
|
|
|
tableView.scrollToRow(at: indexPath, at: .middle, animated: animated)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public func cellForRow(at indexPath: IndexPath) -> UIView? {
|
|
|
|
|
tableView.cellForRow(at: indexPath)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public func getIndexPathFor(molecule: MoleculeModelProtocol) -> IndexPath? {
|
|
|
|
|
guard let molecule = molecule as? (ListItemModelProtocol & MoleculeModelProtocol) else { return nil }
|
|
|
|
|
return (self as? MoleculeListTemplate)?.getIndexPath(for: molecule)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extension ThreeLayerViewController: RotorScrollDelegateProtocol {
|
|
|
|
|
@ -244,3 +290,19 @@ extension ThreeLayerViewController: RotorScrollDelegateProtocol {
|
|
|
|
|
scrollView?.scrollRectToVisible(rect, animated: animated)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extension ThreeLayerCollectionViewController: RotorListTypeDelegateProtocol {
|
|
|
|
|
|
|
|
|
|
public func scrollToRow(at indexPath: IndexPath, animated: Bool) {
|
|
|
|
|
collectionView?.scrollToItem(at: indexPath, at: .centeredVertically, animated: animated)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public func cellForRow(at indexPath: IndexPath) -> UIView? {
|
|
|
|
|
collectionView?.cellForItem(at: indexPath)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public func getIndexPathFor(molecule: MoleculeModelProtocol) -> IndexPath? {
|
|
|
|
|
guard let molecule = molecule as? (CollectionItemModelProtocol & MoleculeModelProtocol) else { return nil }
|
|
|
|
|
return (self as? MoleculeCollectionListProtocol)?.getIndexPath(for: molecule)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|