diff --git a/MVMCoreUI/Molecules/ButtonView.swift b/MVMCoreUI/Molecules/ButtonView.swift index c01a48ce..6f81ce9c 100644 --- a/MVMCoreUI/Molecules/ButtonView.swift +++ b/MVMCoreUI/Molecules/ButtonView.swift @@ -63,6 +63,10 @@ import UIKit primaryButton?.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) } + public static func estimatedHeight(forRow json: [AnyHashable : Any]?) -> CGFloat { + return 40 + } + // MARK: - Constraining func setupButton() { if let primaryButton = primaryButton, !subviews.contains(primaryButton) { diff --git a/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h b/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h index 8b7a3745..67e64d24 100644 --- a/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h +++ b/MVMCoreUI/Molecules/MVMCoreUIMoleculeViewProtocol.h @@ -27,7 +27,7 @@ - (UIStackViewAlignment)moleculeAlignment; // For the molecule list to load more efficiently. -+ (CGFloat)estimatedHeightForRow; ++ (CGFloat)estimatedHeightForRow:(nullable NSDictionary *)json; @end diff --git a/MVMCoreUI/Molecules/MoleculeTableViewCell.swift b/MVMCoreUI/Molecules/MoleculeTableViewCell.swift index f84776f4..e0401f38 100644 --- a/MVMCoreUI/Molecules/MoleculeTableViewCell.swift +++ b/MVMCoreUI/Molecules/MoleculeTableViewCell.swift @@ -91,6 +91,15 @@ import UIKit } } + public static func estimatedHeight(forRow json: [AnyHashable : Any]?) -> CGFloat { + guard let moleculeJSON = json?.optionalDictionaryForKey(KeyMolecule), + let theClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: moleculeJSON, delegateObject: nil), + let estimatedHeightFor = theClass.estimatedHeight else { + return 0 + } + return estimatedHeightFor(moleculeJSON) + } + // MARK: - Arrow /// Adds the standard mvm style caret to the accessory view public func addCaretViewAccessory() { diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.h b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.h index 4b5cb909..4e7dd8db 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.h +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.h @@ -15,7 +15,7 @@ @interface MVMCoreUIMoleculeMappingObject : NSObject /// Maps molecule name to class. -@property (nullable, strong, nonatomic) NSMutableDictionary *moleculeMapping; +@property (nonnull, strong, nonatomic) NSMutableDictionary *moleculeMapping; /// Returns the shared instance + (nullable instancetype)sharedMappingObject; diff --git a/MVMCoreUI/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Templates/MoleculeListTemplate.swift index 1bc590e0..11e3bddb 100644 --- a/MVMCoreUI/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Templates/MoleculeListTemplate.swift @@ -9,10 +9,15 @@ import UIKit open class MoleculeListTemplate: ThreeLayerTableViewController { + var molecules: [(molecule: [AnyHashable: Any]?, moduleName: String?, error: MVMCoreErrorObject?)]? open override func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject, error: AutoreleasingUnsafeMutablePointer) -> Bool { var shouldFinish = super.shouldFinishProcessingLoad(loadObject, error: error) - if shouldFinish, let firstError = modulesNeeded().errors?.first { + guard shouldFinish else { + return shouldFinish + } + + if let firstError = setup()?.first { // Don't continue if there was an error loading needed modules. error.pointee = firstError shouldFinish = false @@ -38,48 +43,49 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { open override func newDataBuildScreen() { super.newDataBuildScreen() + _ = setupMoleculeList() registerWithTable() } // MARK: - table open override func registerWithTable() { super.registerWithTable() - guard let molecules = loadObject?.pageJSON?.arrayForKey(KeyMolecules) else { + guard let molecules = molecules else { return } - for case let map as Dictionary in molecules { - if let molecule = getMoleculeInfo(with: map), let moleculeToRegister = molecule.name { - tableView?.register(molecule.class, forCellReuseIdentifier: moleculeToRegister) + for molecule in molecules { + if let moleculeInfo = getMoleculeInfo(with: molecule), let moleculeToRegister = moleculeInfo.name { + tableView?.register(moleculeInfo.class, forCellReuseIdentifier: moleculeToRegister) } } } open override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { - guard let map = loadObject?.pageJSON?.optionalDictionaryWithChainOfKeysOrIndexes([KeyMolecules,indexPath.row]), - let molecule = getMoleculeInfo(with: map), - let estimatedHeightForRow = molecule.class.estimatedHeightForRow else { + guard let molecule = molecules?[indexPath.row], + let moleculeInfo = getMoleculeInfo(with: molecule), + let estimatedHeight = moleculeInfo.class.estimatedHeight else { return 0 } - return estimatedHeightForRow() + return estimatedHeight(molecule.molecule) } open override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return loadObject?.pageJSON?.arrayForKey(KeyMolecules).count ?? 0 + return molecules?.count ?? 0 } open override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let delegate = delegateObject() as? MVMCoreUIDelegateObject - guard let map = loadObject?.pageJSON?.optionalDictionaryWithChainOfKeysOrIndexes([KeyMolecules,indexPath.row]), - let moleculeJSON = MVMCoreUIMoleculeMappingObject.getMoleculeJSON(for: map, delegateObject: delegate)?.molecule, - let moleculeName = moleculeJSON.optionalStringForKey(KeyMoleculeName), + guard let molecule = molecules?[indexPath.row], + let moleculeInfo = getMoleculeInfo(with: molecule), + let moleculeName = moleculeInfo.name, let cell = tableView.dequeueReusableCell(withIdentifier: moleculeName) else { return UITableViewCell() } + let delegate = delegateObject() as? MVMCoreUIDelegateObject if let protocolCell = cell as? MoleculeListCellProtocol { protocolCell.setSeparatorWithJSON?(loadObject?.pageJSON?.optionalDictionaryForKey("separator"), delegateObject: delegate, additionalData: nil, indexPath: indexPath) } if let protocolCell = cell as? MVMCoreUIMoleculeViewProtocol { - protocolCell.setWithJSON(moleculeJSON, delegateObject: delegate, additionalData: nil) + protocolCell.setWithJSON(molecule.molecule, delegateObject: delegate, additionalData: nil) protocolCell.updateView(tableView.bounds.width) } return cell @@ -101,34 +107,78 @@ open class MoleculeListTemplate: ThreeLayerTableViewController { open override func modulesToListenFor() -> [Any]? { // Get all of the molecules that need modules. - return modulesNeeded().modules + return modulesNeeded() } // MARK: - Module Molecule Handling /// Returns the (name, class) of the molecule for the given map. - func getMoleculeInfo(with map: [AnyHashable: Any]?) -> (name: String?, class: AnyClass)? { - guard let map = map, let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.getMoleculeClass(withJSON: map, delegateObject: delegateObject() as? MVMCoreUIDelegateObject) else { + func getMoleculeInfo(with molecule: (molecule: [AnyHashable: Any]?, moduleName: String?, error: MVMCoreErrorObject?)) -> (name: String?, class: AnyClass)? { + guard let map = molecule.molecule, let moleculeName = map.optionalStringForKey(KeyMoleculeName), let moleculeClass = MVMCoreUIMoleculeMappingObject.shared()?.moleculeMapping[moleculeName] as? AnyClass else { return nil } if let moleculeClass = moleculeClass as? MoleculeListCellProtocol.Type, let moleculeName = moleculeClass.moleculeName { return (moleculeName(map, loadObject), moleculeClass) } else { - return (map.optionalStringForKey(KeyMoleculeName), moleculeClass) + return (moleculeName, moleculeClass) } } - func modulesNeeded() -> (modules: [String]?, errors: [MVMCoreErrorObject]?) { - var modules: [String]? = [] - var errors: [MVMCoreErrorObject]? = [] + /// Sets up the molecule list and ensures no errors loading all content. + func setupMoleculeList() -> [MVMCoreErrorObject]? { + var errors: [MVMCoreErrorObject] = [] let delegate = delegateObject() as? MVMCoreUIDelegateObject - - let _ = MVMCoreUIMoleculeMappingObject.getMoleculeJSON(for: loadObject?.pageJSON?.optionalDictionaryForKey("header"), delegateObject: delegate, moduleNames: &modules, errors: &errors) - let _ = MVMCoreUIMoleculeMappingObject.getMoleculeJSON(for: loadObject?.pageJSON?.optionalDictionaryForKey("footer"), delegateObject: delegate, moduleNames: &modules, errors: &errors) + var moleculeList: [(molecule: [AnyHashable: Any]?, moduleName: String?, error: MVMCoreErrorObject?)] = [] if let molecules = loadObject?.pageJSON?.optionalArrayForKey(KeyMolecules) as? [[AnyHashable: Any]] { for molecule in molecules { - let _ = MVMCoreUIMoleculeMappingObject.getMoleculeJSON(for: molecule, delegateObject: delegateObject() as? MVMCoreUIDelegateObject, moduleNames: &modules, errors: &errors) + if let object = MVMCoreUIMoleculeMappingObject.getMoleculeJSON(for: molecule, delegateObject: delegate) { + if let error = object.error { + errors.append(error) + } else { + moleculeList.append(object) + } + } } } - return (modules?.count ?? 0 > 0 ? modules : nil, errors?.count ?? 0 > 0 ? errors : nil) + molecules = moleculeList + return errors.count > 0 ? errors : nil + } + + /// Sets up the header, footer, molecule list and ensures no errors loading all content. + func setup() -> [MVMCoreErrorObject]? { + var errors: [MVMCoreErrorObject] = [] + let delegate = delegateObject() as? MVMCoreUIDelegateObject + MoleculeListTemplate.addToErrorList(with: MVMCoreUIMoleculeMappingObject.getMoleculeJSON(for: loadObject?.pageJSON?.optionalDictionaryForKey("header"), delegateObject: delegate), errors: &errors) + MoleculeListTemplate.addToErrorList(with: MVMCoreUIMoleculeMappingObject.getMoleculeJSON(for: loadObject?.pageJSON?.optionalDictionaryForKey("footer"), delegateObject: delegate), errors: &errors) + if let newErrors = setupMoleculeList() { + errors.append(contentsOf: newErrors) + } + return errors.count > 0 ? errors : nil + } + + static func addToErrorList(with moleculeObject: (molecule: [AnyHashable: Any]?, moduleName: String?, error: MVMCoreErrorObject?)?, errors: inout [MVMCoreErrorObject]) { + if let error = moleculeObject?.error { + errors.append(error) + } + } + + static func addToModuleList(with moleculeObject: (molecule: [AnyHashable: Any]?, moduleName: String?, error: MVMCoreErrorObject?)?, moduleNames: inout [String]) { + if let moduleName = moleculeObject?.moduleName { + moduleNames.append(moduleName) + } + } + + /// Gets a list of required modules + func modulesNeeded() -> [String]? { + var modules: [String] = [] + let delegate = delegateObject() as? MVMCoreUIDelegateObject + + MoleculeListTemplate.addToModuleList(with: MVMCoreUIMoleculeMappingObject.getMoleculeJSON(for: loadObject?.pageJSON?.optionalDictionaryForKey("header"), delegateObject: delegate), moduleNames: &modules) + MoleculeListTemplate.addToModuleList(with: MVMCoreUIMoleculeMappingObject.getMoleculeJSON(for: loadObject?.pageJSON?.optionalDictionaryForKey("footer"), delegateObject: delegate), moduleNames: &modules) + if let molecules = molecules { + for molecule in molecules { + MoleculeListTemplate.addToModuleList(with: molecule, moduleNames: &modules) + } + } + return (modules.count > 0 ? modules : nil) } }