split handlenewdata from updateUI. refactor behavior constuction. molecule iteration bug fixes. removing AnyObject requirement from ParentMoleculeProtocol.

This commit is contained in:
Hedden, Kyle Matthew 2023-09-15 17:39:36 -04:00
parent 41ce041a0d
commit cd35990a95
24 changed files with 121 additions and 88 deletions

View File

@ -382,7 +382,7 @@
D23A8FEE26122F7D007E14CE /* VisibleBehaviorForVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FED26122F7D007E14CE /* VisibleBehaviorForVideo.swift */; };
D23A8FF82612308D007E14CE /* PageBehaviorProtocolRequirer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */; };
D23A8FFB26123189007E14CE /* PageBehaviorModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FFA26123189007E14CE /* PageBehaviorModelProtocol.swift */; };
D23A90002612347A007E14CE /* PageBehaviorHandlerModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FFF2612347A007E14CE /* PageBehaviorHandlerModelProtocol.swift */; };
D23A90002612347A007E14CE /* PageBehaviorConatinerModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A8FFF2612347A007E14CE /* PageBehaviorConatinerModelProtocol.swift */; };
D23A9004261234CE007E14CE /* PageBehaviorHandlerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A9003261234CE007E14CE /* PageBehaviorHandlerProtocol.swift */; };
D23A900926125FFB007E14CE /* GetContactBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A900826125FFB007E14CE /* GetContactBehavior.swift */; };
D23A90682614B0B4007E14CE /* CoreUIModelMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = D23A90672614B0B4007E14CE /* CoreUIModelMapping.swift */; };
@ -969,7 +969,7 @@
D23A8FED26122F7D007E14CE /* VisibleBehaviorForVideo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibleBehaviorForVideo.swift; sourceTree = "<group>"; };
D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorProtocolRequirer.swift; sourceTree = "<group>"; };
D23A8FFA26123189007E14CE /* PageBehaviorModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorModelProtocol.swift; sourceTree = "<group>"; };
D23A8FFF2612347A007E14CE /* PageBehaviorHandlerModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorHandlerModelProtocol.swift; sourceTree = "<group>"; };
D23A8FFF2612347A007E14CE /* PageBehaviorConatinerModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorConatinerModelProtocol.swift; sourceTree = "<group>"; };
D23A9003261234CE007E14CE /* PageBehaviorHandlerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageBehaviorHandlerProtocol.swift; sourceTree = "<group>"; };
D23A900826125FFB007E14CE /* GetContactBehavior.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetContactBehavior.swift; sourceTree = "<group>"; };
D23A90672614B0B4007E14CE /* CoreUIModelMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreUIModelMapping.swift; sourceTree = "<group>"; };
@ -1202,10 +1202,10 @@
D2E2A9A023E095AB000B42E6 /* ButtonModelProtocol.swift */,
014AA72323C501E2006F3E93 /* ContainerModelProtocol.swift */,
D23EA7FA2475F09800D60C34 /* CarouselItemProtocol.swift */,
01EB3683236097C0006832FA /* MoleculeModelProtocol.swift */,
012A88C3238D86E600FE3DA1 /* CarouselItemModelProtocol.swift */,
012A88B0238C880100FE3DA1 /* CarouselPagingModelProtocol.swift */,
EA05EFAA278DE53600828819 /* ClearableModelProtocol.swift */,
01EB3683236097C0006832FA /* MoleculeModelProtocol.swift */,
012A889B23889E8400FE3DA1 /* TemplateModelProtocol.swift */,
D28A837823C7D5BC00DFE4FC /* PageModelProtocol.swift */,
011B58EF23A2AA980085F53C /* ListItemModelProtocol.swift */,
@ -1321,7 +1321,7 @@
children = (
D23A8FF72612308D007E14CE /* PageBehaviorProtocolRequirer.swift */,
D23A8FFA26123189007E14CE /* PageBehaviorModelProtocol.swift */,
D23A8FFF2612347A007E14CE /* PageBehaviorHandlerModelProtocol.swift */,
D23A8FFF2612347A007E14CE /* PageBehaviorConatinerModelProtocol.swift */,
D23A9003261234CE007E14CE /* PageBehaviorHandlerProtocol.swift */,
27F973522466074500CAB5C5 /* PageBehaviorProtocol.swift */,
);
@ -2720,7 +2720,7 @@
01EB369423609801006832FA /* HeadlineBodyModel.swift in Sources */,
D2A92884241ACB25004E01C6 /* ProgrammaticScrollViewController.swift in Sources */,
EA985C3E2970938F00F2FF2E /* Tilelet.swift in Sources */,
D23A90002612347A007E14CE /* PageBehaviorHandlerModelProtocol.swift in Sources */,
D23A90002612347A007E14CE /* PageBehaviorConatinerModelProtocol.swift in Sources */,
EAA78020290081320057DFDF /* VDSMoleculeViewProtocol.swift in Sources */,
0A21DB7F235DECC500C160A2 /* EntryField.swift in Sources */,
D2E2A99F23E07F8A000B42E6 /* PillButton.swift in Sources */,

View File

@ -12,13 +12,15 @@ public protocol MoleculeContainerModelProtocol: ContainerModelProtocol, ParentMo
}
public extension MoleculeContainerModelProtocol {
var children: [MoleculeModelProtocol] {
return [molecule]
}
}
public extension MoleculeContainerModelProtocol where Self: AnyObject {
func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool {
mutating func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool {
return try replaceChildMolecule(at: &molecule, with: replacementMolecule)
}
}

View File

@ -16,11 +16,12 @@ public protocol StackModelProtocol: ParentMoleculeModelProtocol {
}
extension StackModelProtocol {
public var children: [MoleculeModelProtocol] { return molecules }
}
extension StackModelProtocol where Self: AnyObject {
public func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
public mutating func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool {
return try replaceChildMolecule(in: &molecules, with: molecule)
}
}

View File

@ -68,8 +68,13 @@ public extension Array where Element == MoleculeModelProtocol {
}
func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol, inout Bool) -> Void) {
forEach { (molecule) in
molecule.depthFirstTraverse(options: options, depth: depth, onVisit: onVisit)
var shouldStop = false
for molecule in self {
molecule.depthFirstTraverse(options: options, depth: depth) { depth, molecule, stop in
onVisit(depth, molecule, &shouldStop)
stop = shouldStop
}
if shouldStop { break }
}
}
}

View File

@ -8,15 +8,15 @@
import Foundation
public protocol ParentModelProtocol: MoleculeTreeTraversalProtocol, AnyObject {
public protocol ParentModelProtocol: MoleculeTreeTraversalProtocol {
var children: [MoleculeModelProtocol] { get }
/// Method for replacing surface level children. (Does not recurse.)
func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool
mutating func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool
}
public extension ParentModelProtocol {
public extension ParentModelProtocol where Self: AnyObject {
/// Top level test to replace child molecules. Each parent molecule should attempt to replace.
func replaceChildMolecule(with molecule: MoleculeModelProtocol) throws -> Bool { return false }

View File

@ -37,9 +37,12 @@ public extension TemplateModelProtocol {
func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol, inout Bool) -> Void) {
return rootMolecules.depthFirstTraverse(options: options, depth: depth, onVisit: onVisit)
}
}
extension TemplateModelProtocol {
/// Recursively finds and replaces the first child matching the replacement molecule id property.
func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool {
mutating func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) throws -> Bool {
// Attempt root level replacement on the template model first.
if try replaceChildMolecule(with: replacementMolecule) {
return true
@ -49,7 +52,7 @@ public extension TemplateModelProtocol {
var possibleError: Error?
// Dive into each root thereafter.
depthFirstTraverse(options: .parentFirst, depth: 0) { depth, molecule, stop in
guard let parentMolecule = molecule as? ParentMoleculeModelProtocol else { return }
guard var parentMolecule = molecule as? ParentMoleculeModelProtocol else { return }
do {
didReplaceMolecule = try parentMolecule.replaceChildMolecule(with: replacementMolecule)
} catch {

View File

@ -8,7 +8,7 @@
import Foundation
public protocol TemplateProtocol: AnyObject, PageProtocol {
public protocol TemplateProtocol: AnyObject, PageProtocol{
associatedtype TemplateModel: TemplateModelProtocol
var templateModel: TemplateModel? { get set }
@ -28,25 +28,26 @@ public extension TemplateProtocol {
}
}
func decodeTemplate(using decoder: JSONDecoder, from data: Data) throws -> TemplateModel {
try decoder.decode(TemplateModel.self, from: data)
}
}
public extension TemplateProtocol where Self: PageBehaviorHandlerProtocol, Self: MVMCoreViewControllerProtocol {
/// Helper function to do common parsing logic.
func parseTemplate(json: [AnyHashable: Any]?) throws {
guard let pageJSON = json else { return }
let delegateObject = (self as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject
let delegateObject = delegateObject?() as? MVMCoreUIDelegateObject
let data = try JSONSerialization.data(withJSONObject: pageJSON)
let decoder = JSONDecoder.create(with: delegateObject)
templateModel = try decodeTemplate(using: decoder, from: data)
// Add additional required behaviors if applicable.
guard var behaviorHandlerModel = templateModel as? TemplateModelProtocol & PageBehaviorHandlerModelProtocol,
var behaviorHandler = self as? PageBehaviorHandlerProtocol else { return }
behaviorHandlerModel.traverseAndAddRequiredBehaviors()
behaviorHandler.createBehaviors(for: behaviorHandlerModel, delegateObject: delegateObject)
if let viewController = self as? UIViewController {
MVMCoreUISession.sharedGlobal()?.applyGlobalBehaviors(to: viewController)
}
}
func decodeTemplate(using decoder: JSONDecoder, from data: Data) throws -> TemplateModel {
try decoder.decode(TemplateModel.self, from: data)
guard var pageBehaviorsModel = templateModel as? TemplateModelProtocol & PageBehaviorConatinerModelProtocol else { return }
pageBehaviorsModel.traverseAndAddRequiredBehaviors()
var behaviorHandler = self
behaviorHandler.applyBehaviors(pageBehaviorModel: pageBehaviorsModel, delegateObject: delegateObject)
}
}

View File

@ -80,12 +80,12 @@
}
open override func handleNewData() {
open override func updateUI() {
topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false
bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false
setup()
registerCells()
super.handleNewData()
super.updateUI()
}
//--------------------------------------------------

View File

@ -21,8 +21,8 @@ open class ModalSectionListTemplate: SectionListTemplate {
// MARK: - Lifecycle
//--------------------------------------------------
override open func handleNewData() {
super.handleNewData()
override open func updateUI() {
super.updateUI()
_ = MVMCoreUICommonViewsUtility.addCloseButton(to: view, action: { [weak self] _ in
guard let self = self else { return }
let closeAction = (self.templateModel as? ModalSectionListTemplateModel)?.closeAction ??

View File

@ -81,12 +81,12 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
return molecule
}
open override func handleNewData() {
open override func updateUI() {
topViewOutsideOfScrollArea = templateModel?.anchorHeader ?? false
bottomViewOutsideOfScrollArea = templateModel?.anchorFooter ?? false
setup()
registerWithTable()
super.handleNewData()
super.updateUI()
}
open override func viewDidAppear(_ animated: Bool) {
@ -300,7 +300,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol
}
extension MoleculeListTemplate: MoleculeListProtocol {
open func removeMolecules(at indexPaths: [IndexPath], animation: UITableView.RowAnimation?) {
public func removeMolecules(at indexPaths: [IndexPath], animation: UITableView.RowAnimation?) {
for (index, indexPath) in indexPaths.sorted().enumerated() {
let removeIndex = indexPath.row - index
moleculesInfo?.remove(at: removeIndex)
@ -313,7 +313,7 @@ extension MoleculeListTemplate: MoleculeListProtocol {
view.layoutIfNeeded()
}
open func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation?) {
public func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation?) {
var indexPaths: [IndexPath] = []
for molecule in molecules {
@ -332,7 +332,7 @@ extension MoleculeListTemplate: MoleculeListProtocol {
self.view.layoutIfNeeded()
}
open func getIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol) -> IndexPath? {
public func getIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol) -> IndexPath? {
guard let index = moleculesInfo?.firstIndex(where: { (moleculeInfo) -> Bool in
return equal(moleculeA: molecule, moleculeB: moleculeInfo.molecule)
}) else { return nil }

View File

@ -26,8 +26,8 @@
}
}
open override func handleNewData() {
super.handleNewData()
open override func updateUI() {
super.updateUI()
heightConstraint?.isActive = true
}
}

View File

@ -19,10 +19,10 @@ import UIKit
try super.parsePageJSON()
}
open override func handleNewData() {
open override func updateUI() {
topViewOutsideOfScroll = templateModel?.anchorHeader ?? false
bottomViewOutsideOfScroll = templateModel?.anchorFooter ?? false
super.handleNewData()
super.updateUI()
}
open override func viewForTop() -> UIView? {

View File

@ -9,6 +9,6 @@
import Foundation
public protocol MVMControllerModelProtocol: TemplateModelProtocol, FormHolderModelProtocol, PageBehaviorHandlerModelProtocol {
public protocol MVMControllerModelProtocol: TemplateModelProtocol, FormHolderModelProtocol, PageBehaviorConatinerModelProtocol {
}

View File

@ -63,8 +63,8 @@ open class ScrollingViewController: ViewController {
registerForKeyboardNotifications()
}
open override func handleNewData() {
super.handleNewData()
open override func updateUI() {
super.updateUI()
// will change scrollView indicatorStyle automatically on the basis of backgroundColor
var greyScale: CGFloat = 0
if view.backgroundColor?.getWhite(&greyScale, alpha: nil) ?? false {

View File

@ -108,8 +108,8 @@ import Foundation
}
}
open override func handleNewData() {
super.handleNewData()
open override func updateUI() {
super.updateUI()
topView?.removeFromSuperview()
bottomView?.removeFromSuperview()
topView = viewForTop()

View File

@ -50,8 +50,8 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController {
tableView.reloadData()
}
open override func handleNewData() {
super.handleNewData()
open override func updateUI() {
super.updateUI()
createViewForTableHeader()
createViewForTableFooter()
tableView?.reloadData()

View File

@ -49,8 +49,8 @@ open class ThreeLayerViewController: ProgrammaticScrollViewController {
}
}
open override func handleNewData() {
super.handleNewData()
open override func updateUI() {
super.updateUI()
// Removes the views
topView?.removeFromSuperview()

View File

@ -111,9 +111,12 @@ import MVMCore
guard newData else { return }
do {
// TODO: Parse parsePageJSON modifies the page model on a different thread than
// the UI update which could cause discrepancies. Parse should return the resulting
// object and assignment should be synchronized on handleNewData(model: ).
try parsePageJSON()
MVMCoreDispatchUtility.performBlock(onMainThread: {
self.handleNewDataAndUpdateUI()
self.handleNewData()
})
} catch {
if let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: "updateJSON for pageType: \(String(describing: pageType))") {
@ -160,7 +163,7 @@ import MVMCore
}
return false
}
return true
}
@ -229,9 +232,12 @@ import MVMCore
return true
}
/// Calls processNewData and then sets the ui to update with updateView
open func handleNewDataAndUpdateUI() {
handleNewData()
@MainActor
open func updateUI() {
if let backgroundColor = model?.backgroundColor {
view.backgroundColor = backgroundColor.uiColor
}
needsUpdateUI = true
view.setNeedsLayout()
}
@ -244,6 +250,7 @@ import MVMCore
}
/// Processes any new data. Called after the page is loaded the first time and on response updates for this page,
@MainActor
open func handleNewData() {
if model?.navigationBar == nil {
let navigationItem = createDefaultLegacyNavigationModel()
@ -259,12 +266,10 @@ import MVMCore
formValidator = FormValidator(rules)
}
if let backgroundColor = model?.backgroundColor {
view.backgroundColor = backgroundColor.uiColor
}
// Notify the manager of new data
manager?.newDataReceived?(in: self)
updateUI()
}
public func generateMoleculeView(from model: MoleculeModelProtocol) -> MoleculeViewProtocol? {
@ -333,7 +338,7 @@ import MVMCore
initialLoad()
}
handleNewDataAndUpdateUI()
handleNewData()
}
open override func viewDidLayoutSubviews() {

View File

@ -43,7 +43,7 @@ public class PageGetContactBehavior: PageVisibilityBehavior {
MVMCoreDispatchUtility.performBlock(onMainThread: {
// TODO: move to protocol function instead
guard let controller = self?.delegate?.moleculeDelegate as? ViewController else { return }
controller.handleNewDataAndUpdateUI()
controller.handleNewData()
})
}
}

View File

@ -45,11 +45,12 @@ public class GetNotificationAuthStatusBehavior: PageVisibilityBehavior {
for consumer in consumers {
consumer.consume(notificationStatus: settings.authorizationStatus)
}
// Tell template to update
MVMCoreDispatchUtility.performBlock(onMainThread: {
Task {
// Tell template to update
guard let controller = self.delegate?.moleculeDelegate as? ViewController else { return }
controller.handleNewDataAndUpdateUI()
})
await controller.handleNewData()
}
}
}

View File

@ -1,16 +1,17 @@
//
// PageBehaviorHandlerModelProtocol.swift
// PageBehaviorConatinerModelProtocol.swift
// MVMCoreUI
//
// Created by Scott Pfeil on 3/29/21.
// Copyright © 2021 Verizon Wireless. All rights reserved.
//
public protocol PageBehaviorHandlerModelProtocol {
/// Protocol applied to a model that contains a list of behavior models.
public protocol PageBehaviorConatinerModelProtocol {
var behaviors: [PageBehaviorModelProtocol]? { get set }
}
public extension PageBehaviorHandlerModelProtocol {
public extension PageBehaviorConatinerModelProtocol {
/// Adds the behavior model to the behaviors if possible.
mutating func add(behavior: PageBehaviorModelProtocol) {
@ -24,7 +25,7 @@ public extension PageBehaviorHandlerModelProtocol {
}
}
public extension PageBehaviorHandlerModelProtocol where Self: MoleculeTreeTraversalProtocol {
public extension PageBehaviorConatinerModelProtocol where Self: MoleculeTreeTraversalProtocol {
/// Traverses all models and adds any required behavior models.
mutating func traverseAndAddRequiredBehaviors() {

View File

@ -12,20 +12,10 @@ public protocol PageBehaviorHandlerProtocol {
}
public extension PageBehaviorHandlerProtocol {
/// Creates the behaviors and sets the variable.
mutating func createBehaviors(for model: PageBehaviorHandlerModelProtocol, delegateObject: MVMCoreUIDelegateObject?) {
behaviors = behaviors?.filter { $0.transcendsPageUpdates }
if behaviors?.isEmpty ?? false {
behaviors = nil
}
guard let behaviorModels = model.behaviors else {
return
}
var behaviors: [PageBehaviorProtocol] = behaviors ?? []
func createBehaviors(for behaviorModels: [PageBehaviorModelProtocol], delegateObject: MVMCoreUIDelegateObject?) -> [PageBehaviorProtocol] {
var behaviors = [PageBehaviorProtocol]()
for behaviorModel in behaviorModels {
do {
let handlerType = try ModelRegistry.getHandler(behaviorModel) as! PageBehaviorProtocol.Type
@ -37,7 +27,23 @@ public extension PageBehaviorHandlerProtocol {
}
}
}
return behaviors
}
mutating func applyBehaviors(pageBehaviorModel: PageBehaviorConatinerModelProtocol, delegateObject: MVMCoreUIDelegateObject?) {
// Pull the existing behaviors.
var behaviors = (behaviors ?? []).filter { $0.transcendsPageUpdates }
// Create and append any new behaviors based on the incoming models.
let newBehaviors = createBehaviors(for: pageBehaviorModel.behaviors ?? [], delegateObject: delegateObject)
behaviors.append(contentsOf: newBehaviors)
// Apply them to the page.
self.behaviors = behaviors.count > 0 ? behaviors : nil
// Ask the session to apply any more. (Curently inverted contol due to Swift <--> Obj-C conflict.
if let viewController = self as? UIViewController {
MVMCoreUISession.sharedGlobal()?.applyGlobalBehaviors(to: viewController)
}
}
/// Executes all behaviors of type.
@ -49,3 +55,11 @@ public extension PageBehaviorHandlerProtocol {
return try behaviors?.compactMap({$0 as? T}).allSatisfy({ return try behaviourBlock($0) }) ?? true
}
}
public extension PageBehaviorHandlerProtocol where Self: MVMCoreViewControllerProtocol {
mutating func applyBehaviors(pageBehaviorModel: PageBehaviorConatinerModelProtocol) {
applyBehaviors(pageBehaviorModel: pageBehaviorModel, delegateObject: delegateObject?() as? MVMCoreUIDelegateObject)
}
}

View File

@ -86,8 +86,8 @@ public protocol PageCustomActionHandlerBehavior: PageBehaviorProtocol {
}
public extension MVMCoreUIDelegateObject {
var behaviorModelDelegate: PageBehaviorHandlerModelProtocol? {
(moleculeDelegate as? PageProtocol)?.pageModel as? PageBehaviorHandlerModelProtocol
var behaviorModelDelegate: PageBehaviorConatinerModelProtocol? {
(moleculeDelegate as? PageProtocol)?.pageModel as? PageBehaviorConatinerModelProtocol
}
weak var behaviorTemplateDelegate: (PageBehaviorHandlerProtocol & NSObjectProtocol)? {

View File

@ -24,7 +24,7 @@ public class ReplaceableMoleculeBehavior: PageMoleculeTransformationBehavior {
public func onPageNew(rootMolecules: [MoleculeModelProtocol], _ delegateObject: MVMCoreUIDelegateObject?) {
guard let templateModel = delegateObject?.moleculeDelegate?.getTemplateModel() else { return }
guard var templateModel = delegateObject?.moleculeDelegate?.getTemplateModel() else { return }
templateModel.printMolecules()