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() { public override func main() {
guard UIAccessibility.isVoiceOverRunning, !checkAndHandleForCancellation() else { guard UIAccessibility.isVoiceOverRunning else {
stop() markAsFinished()
return return
} }
Task { @MainActor [weak self] in Task { @MainActor [weak self] in
guard let self = self, !self.isCancelled else { guard let self = self, !self.isCancelled else {
self?.stop() self?.markAsFinished()
return return
} }
UIAccessibility.post(notification: self.notificationType, argument: self.argument) 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 { open class AccessibilityHandler {

View File

@ -67,13 +67,22 @@ public protocol RotorScrollDelegateProtocol: MVMCoreViewControllerProtocol {
} }
public protocol RotorListTypeDelegateProtocol: RotorScrollDelegateProtocol { 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 cellForRow(at indexPath: IndexPath) -> UIView?
func getIndexPathFor(molecule: MoleculeModelProtocol) -> IndexPath?
} }
struct RotorElement { struct RotorElement {
let indexPath: IndexPath
let indexPath: IndexPath?
let model: MoleculeModelProtocol 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 { class RotorHandler {
@ -94,7 +103,9 @@ class RotorHandler {
NotificationCenter.default.publisher(for: UIAccessibility.voiceOverStatusDidChangeNotification) NotificationCenter.default.publisher(for: UIAccessibility.voiceOverStatusDidChangeNotification)
.sink { [weak self] _ in .sink { [weak self] _ in
guard UIAccessibility.isVoiceOverRunning, (self?.rotorElements.isEmpty ?? true) else { return } 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) }.store(in: &anyCancellable)
} }
@ -120,7 +131,7 @@ class RotorHandler {
customRotors.append(rotor) customRotors.append(rotor)
} }
} }
currentViewController.accessibilityCustomRotors = customRotors currentViewController.view.accessibilityCustomRotors = customRotors
} }
private func getTraitMappedElements(_ template: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? { private func getTraitMappedElements(_ template: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? {
@ -143,40 +154,50 @@ class RotorHandler {
} }
private func getRotorElementsFrom(template: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? { private func getRotorElementsFrom(template: (MVMCoreViewControllerProtocol & UIViewController)?, type: RotorType) -> [Any]? {
guard let template = template as? (RotorViewElementsProtocol & MVMCoreViewControllerProtocol & UIViewController) else { 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] 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] let rotorElements = getRotorElements(from: template, type: type)
var reusableViewRotorElements: [Any] = [] 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.
if let middleView = template.middleView { return rotorElements + remainingRotorElements
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
} }
private func getRotorElements(from template: (any MoleculeListProtocol & ViewController)?, type: RotorType) -> [Any]? { private func getRotorElements(from template: (MVMCoreViewControllerProtocol & ViewController & RotorViewElementsProtocol)?, type: RotorType) -> [Any] {
guard let templateModel = template?.model, guard let templateModel = template?.model as? ThreeLayerModelBase else { return [] }
let moleculeList = template else { return nil } var rotorElements: [Any] = []
var rotorElements: [RotorElement] = [] 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? var traitIndexPath: IndexPath?
rotorElements = templateModel.reduceDepthFirstTraverse(options: .parentFirst, depth: 0, initialResult: rotorElements) { result, model, depth in var carouselItemModel: MoleculeModelProtocol?
if let listModel = model as? (ListItemModelProtocol & MoleculeModelProtocol), let indexPath = moleculeList.getIndexPath(for: listModel) { 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 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 var result = result
if type.modelFilter(model), let traitIndexPath { if type.modelFilter(model) {
result.append(.init(indexPath: traitIndexPath, model: model)) result.append(.init(indexPath: traitIndexPath, model: model, carouselItemModel: carouselItemModel))
} }
return result return result
} }
return rotorElements return rotorElements as [Any]
} }
private func createRotor(for type: RotorType) -> UIAccessibilityCustomRotor? { private func createRotor(for type: RotorType) -> UIAccessibilityCustomRotor? {
@ -195,10 +216,28 @@ class RotorHandler {
guard rotorIndex >= 0 else { return UIAccessibilityCustomRotorItemResult() } guard rotorIndex >= 0 else { return UIAccessibilityCustomRotorItemResult() }
var rotorElement = elements[rotorIndex] var rotorElement = elements[rotorIndex]
if let element = rotorElement as? RotorElement, if let element = rotorElement as? RotorElement,
let controller = self.delegate as? RotorListTypeDelegateProtocol { let controller = self.delegate as? (RotorViewElementsProtocol & ViewController) {
controller.scrollToRow(at: element.indexPath, at: .middle, animated: false) var cellView: UIView?
guard let cellView = controller.cellForRow(at: element.indexPath) else { return UIAccessibilityCustomRotorItemResult() } if let indexPath = element.indexPath, let controller = self.delegate as? (any RotorListTypeDelegateProtocol) {
rotorElement = MVMCoreUIUtility.findViews(by: MoleculeViewProtocol.self, views: [cellView]).filter { type.filter($0) && $0.model?.id == element.model.id }.first as Any 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 { } else {
if let viewElement = (rotorElement as? UIView) { if let viewElement = (rotorElement as? UIView) {
let convertedFrame = viewElement.convert(viewElement.frame, to: (self.delegate as? UIViewController)?.view) let convertedFrame = viewElement.convert(viewElement.frame, to: (self.delegate as? UIViewController)?.view)
@ -223,19 +262,26 @@ extension RotorScrollDelegateProtocol {
} }
extension RotorListTypeDelegateProtocol { 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 { extension ThreeLayerTableViewController: RotorListTypeDelegateProtocol {
public func scrollToRow(at indexPath: IndexPath, at scrollPosition: UITableView.ScrollPosition, animated: Bool) { public func scrollToRow(at indexPath: IndexPath, animated: Bool) {
tableView.scrollToRow(at: indexPath, at: scrollPosition, animated: animated) tableView.scrollToRow(at: indexPath, at: .middle, animated: animated)
} }
public func cellForRow(at indexPath: IndexPath) -> UIView? { public func cellForRow(at indexPath: IndexPath) -> UIView? {
tableView.cellForRow(at: indexPath) 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 { extension ThreeLayerViewController: RotorScrollDelegateProtocol {
@ -244,3 +290,19 @@ extension ThreeLayerViewController: RotorScrollDelegateProtocol {
scrollView?.scrollRectToVisible(rect, animated: animated) 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) 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 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 /// Checks if the two molecules are equal
private func equal(moleculeA: MoleculeModelProtocol, moleculeB: MoleculeModelProtocol) -> Bool { private func equal(moleculeA: MoleculeModelProtocol, moleculeB: MoleculeModelProtocol) -> Bool {
// TODO: move this to a better approach, maybe a UUID for each model. moleculeA.id == moleculeB.id
// Do instance check
if let classMoleculeA = moleculeA as? NSObjectProtocol,
let classMoleculeB = moleculeB as? NSObjectProtocol {
return classMoleculeA === classMoleculeB
}
// ID check
return moleculeA.id == moleculeB.id
} }
} }

View File

@ -9,10 +9,14 @@
import Foundation 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. /// 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? public var topView: UIView?
private var bottomView: UIView? public var middleView: UIView? {
set {}
get { collectionView }
}
public var bottomView: UIView?
private var headerView: ContainerCollectionReusableView? private var headerView: ContainerCollectionReusableView?
private var footerView: ContainerCollectionReusableView? private var footerView: ContainerCollectionReusableView?
private let headerID = "header" private let headerID = "header"

View File

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