review comments and enhancements in rotorhandler

This commit is contained in:
Krishna Kishore Bandaru 2023-10-19 10:35:27 +05:30
parent ac287ef379
commit 436d4c746b
7 changed files with 127 additions and 59 deletions

View File

@ -22,13 +22,13 @@ public class AccessibilityOperation: MVMCoreOperation {
}
public override func main() {
guard UIAccessibility.isVoiceOverRunning, !checkAndHandleForCancellation() else {
stop()
guard UIAccessibility.isVoiceOverRunning else {
markAsFinished()
return
}
Task { @MainActor [weak self] in
guard let self = self, !self.isCancelled else {
self?.stop()
self?.markAsFinished()
return
}
UIAccessibility.post(notification: self.notificationType, argument: self.argument)
@ -41,11 +41,6 @@ public class AccessibilityOperation: MVMCoreOperation {
}
}
}
public func stop() {
guard isCancelled else { return }
markAsFinished()
}
}
open class AccessibilityHandler {

View File

@ -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)
}
}

View File

@ -37,3 +37,9 @@ public extension MVMCoreUIDelegateObject {
return (moleculeDelegate as? MoleculeListProtocol & NSObjectProtocol)
}
}
public protocol MoleculeCollectionListProtocol {
/// Asks the delegate for the index of molecule.
func getIndexPath(for molecule: CollectionItemModelProtocol & MoleculeModelProtocol) -> IndexPath?
}

View File

@ -198,3 +198,11 @@
return modules
}
}
extension CollectionTemplate: MoleculeCollectionListProtocol {
public func getIndexPath(for molecule: CollectionItemModelProtocol & MoleculeModelProtocol) -> IndexPath? {
guard let index = (moleculesInfo?.firstIndex { $0.molecule.id == molecule.id }) else { return nil }
return IndexPath(item: index, section: 0)
}
}

View File

@ -288,14 +288,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
/// Checks if the two molecules are equal
private func equal(moleculeA: MoleculeModelProtocol, moleculeB: MoleculeModelProtocol) -> Bool {
// TODO: move this to a better approach, maybe a UUID for each model.
// Do instance check
if let classMoleculeA = moleculeA as? NSObjectProtocol,
let classMoleculeB = moleculeB as? NSObjectProtocol {
return classMoleculeA === classMoleculeB
}
// ID check
return moleculeA.id == moleculeB.id
moleculeA.id == moleculeB.id
}
}

View File

@ -9,10 +9,14 @@
import Foundation
/// A view controller that has three main layers, a header, collection rows, and a footer. The header is added as a supplement header to the first section, and the footer is added as a supplement footer to the last section. This view controller allows for flexible space between the three layers to fit the screeen.
@objc open class ThreeLayerCollectionViewController: ProgrammaticCollectionViewController, UICollectionViewDelegateFlowLayout {
@objc open class ThreeLayerCollectionViewController: ProgrammaticCollectionViewController, UICollectionViewDelegateFlowLayout, RotorViewElementsProtocol {
private var topView: UIView?
private var bottomView: UIView?
public var topView: UIView?
public var middleView: UIView? {
set {}
get { collectionView }
}
public var bottomView: UIView?
private var headerView: ContainerCollectionReusableView?
private var footerView: ContainerCollectionReusableView?
private let headerID = "header"

View File

@ -9,12 +9,12 @@
import UIKit
open class ThreeLayerViewController: ProgrammaticScrollViewController {
open class ThreeLayerViewController: ProgrammaticScrollViewController, RotorViewElementsProtocol {
// The three main views
var topView: UIView?
var middleView: UIView?
var bottomView: UIView?
public var topView: UIView?
public var middleView: UIView?
public var bottomView: UIView?
var useMargins: Bool = false
// The top view can be put outside of the scrolling area.