Merge branch 'bugfix/CXTDT-579050' into 'develop'
Digital PCT265 defect CXTDT-579049: Fix indexing safety to mocules to add.... ### Summary A new attempt at synchronizing data + UI updates. ### JIRA Ticket https://onejira.verizon.com/browse/CXTDT-579050 Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com> See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core_ui/-/merge_requests/1142
This commit is contained in:
commit
82fe0e13de
@ -43,6 +43,9 @@ open class Carousel: View {
|
|||||||
/// The models for the molecules.
|
/// The models for the molecules.
|
||||||
public var molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]?
|
public var molecules: [MoleculeModelProtocol & CarouselItemModelProtocol]?
|
||||||
|
|
||||||
|
/// A list of currently registered cells.
|
||||||
|
public var registeredMoleculeIds: [String]?
|
||||||
|
|
||||||
/// The horizontal alignment of the cell in the collection view. Only noticeable if the itemWidthPercent is less than 100%.
|
/// The horizontal alignment of the cell in the collection view. Only noticeable if the itemWidthPercent is less than 100%.
|
||||||
public var itemAlignment = UICollectionView.ScrollPosition.left
|
public var itemAlignment = UICollectionView.ScrollPosition.left
|
||||||
|
|
||||||
@ -174,9 +177,7 @@ open class Carousel: View {
|
|||||||
MVMCoreLoggingHandler.shared()?.handleDebugMessage("[\(Self.self)] [\(ObjectIdentifier(self).hashValue)]\noriginal model: \(originalModel?.debugDescription ?? "none")\nnew model: \(model)")
|
MVMCoreLoggingHandler.shared()?.handleDebugMessage("[\(Self.self)] [\(ObjectIdentifier(self).hashValue)]\noriginal model: \(originalModel?.debugDescription ?? "none")\nnew model: \(model)")
|
||||||
|
|
||||||
if #available(iOS 15.0, *) {
|
if #available(iOS 15.0, *) {
|
||||||
if let originalModel, carouselModel.isDeeplyVisuallyEquivalent(to: originalModel),
|
if hasSameCellRegistration(with: carouselModel, delegateObject: delegateObject) {
|
||||||
originalModel.visibleMolecules.isVisuallyEquivalent(to: molecules ?? []) // Since the carousel model's children are in place replaced and we do not have a deep copy of this model tree, add in this hack to check if the prior captured carousel items match the newly visible ones.
|
|
||||||
{
|
|
||||||
// Prevents a carousel reset while still updating the cell backing data through reconfigureItems.
|
// Prevents a carousel reset while still updating the cell backing data through reconfigureItems.
|
||||||
MVMCoreLoggingHandler.shared()?.handleDebugMessage("[\(Self.self)] Model is visually equivalent. Skipping rebuild...")
|
MVMCoreLoggingHandler.shared()?.handleDebugMessage("[\(Self.self)] Model is visually equivalent. Skipping rebuild...")
|
||||||
prepareMolecules(with: carouselModel)
|
prepareMolecules(with: carouselModel)
|
||||||
@ -284,12 +285,29 @@ open class Carousel: View {
|
|||||||
|
|
||||||
/// Registers the cells with the collection view
|
/// Registers the cells with the collection view
|
||||||
func registerCells(with carouselModel: CarouselModel, delegateObject: MVMCoreUIDelegateObject?) {
|
func registerCells(with carouselModel: CarouselModel, delegateObject: MVMCoreUIDelegateObject?) {
|
||||||
|
var registeredIds = [String]()
|
||||||
for molecule in carouselModel.molecules {
|
for molecule in carouselModel.molecules {
|
||||||
if let info = getMoleculeInfo(with: molecule, delegateObject: delegateObject) {
|
if let info = getMoleculeInfo(with: molecule, delegateObject: delegateObject) {
|
||||||
collectionView.register(info.class, forCellWithReuseIdentifier: info.identifier)
|
collectionView.register(info.class, forCellWithReuseIdentifier: info.identifier)
|
||||||
|
registeredIds.append(info.identifier)
|
||||||
|
} else {
|
||||||
|
registeredIds.append(molecule.moleculeName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
registeredMoleculeIds = registeredIds
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasSameCellRegistration(with carouselModel: CarouselModel, delegateObject: MVMCoreUIDelegateObject?) -> Bool {
|
||||||
|
guard let registeredMoleculeIds else { return false }
|
||||||
|
|
||||||
|
let incomingIds = carouselModel.molecules.map { molecule in
|
||||||
|
if let info = getMoleculeInfo(with: molecule, delegateObject: delegateObject) {
|
||||||
|
return info.identifier
|
||||||
|
} else {
|
||||||
|
return molecule.moleculeName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return incomingIds == registeredMoleculeIds
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------
|
//--------------------------------------------------
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MVMCore
|
import MVMCore
|
||||||
|
import Combine
|
||||||
|
|
||||||
@objc open class ViewController: UIViewController, MVMCoreViewControllerProtocol, MVMCoreViewManagerViewControllerProtocol, MoleculeDelegateProtocol, FormHolderProtocol, MVMCoreActionDelegateProtocol, ActionDelegateProtocol, MVMCoreLoadDelegateProtocol, UITextFieldDelegate, UITextViewDelegate, ObservingTextFieldDelegate, MVMCoreUIDetailViewProtocol, PageProtocol, PageBehaviorHandlerProtocol {
|
@objc open class ViewController: UIViewController, MVMCoreViewControllerProtocol, MVMCoreViewManagerViewControllerProtocol, MoleculeDelegateProtocol, FormHolderProtocol, MVMCoreActionDelegateProtocol, ActionDelegateProtocol, MVMCoreLoadDelegateProtocol, UITextFieldDelegate, UITextViewDelegate, ObservingTextFieldDelegate, MVMCoreUIDetailViewProtocol, PageProtocol, PageBehaviorHandlerProtocol {
|
||||||
|
|
||||||
@ -38,7 +39,7 @@ import MVMCore
|
|||||||
public var behaviors: [PageBehaviorProtocol]?
|
public var behaviors: [PageBehaviorProtocol]?
|
||||||
|
|
||||||
public var needsUpdateUI = false
|
public var needsUpdateUI = false
|
||||||
private var observingForResponses: NSObjectProtocol?
|
private var observingForResponses: AnyCancellable?
|
||||||
private var initialLoadFinished = false
|
private var initialLoadFinished = false
|
||||||
public var isFirstRender = true
|
public var isFirstRender = true
|
||||||
public var previousScreenSize = CGSize.zero
|
public var previousScreenSize = CGSize.zero
|
||||||
@ -66,9 +67,28 @@ import MVMCore
|
|||||||
(pagesToListenFor()?.count ?? 0 > 0 || modulesToListenFor()?.count ?? 0 > 0)
|
(pagesToListenFor()?.count ?? 0 > 0 || modulesToListenFor()?.count ?? 0 > 0)
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
observingForResponses = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: NotificationResponseLoaded), object: nil, queue: pageUpdateQueue) { [weak self] notification in
|
observingForResponses = NotificationCenter.default.publisher(for: NSNotification.Name(rawValue: NotificationResponseLoaded))
|
||||||
self?.responseJSONUpdated(notification: notification)
|
.receive(on: self.pageUpdateQueue) // Background serial queue.
|
||||||
}
|
.compactMap { [weak self] notification in
|
||||||
|
self?.pullUpdates(from: notification) ?? nil
|
||||||
|
}
|
||||||
|
// Merge all page and module updates into one update event.
|
||||||
|
.scan((nil, nil, nil)) { accumulator, next in
|
||||||
|
// Always take the latest page and the latest modules with same key.
|
||||||
|
return (next.0 ?? accumulator.0, next.1 ?? accumulator.1, next.2?.mergingRight(accumulator.2 ?? [:]))
|
||||||
|
}
|
||||||
|
// Delay allowing the previous model update to settle before triggering a re-render.
|
||||||
|
.throttle(for: .seconds(0.05), scheduler: RunLoop.main, latest: true)
|
||||||
|
.sink { [weak self] (pageUpdates: [String : Any]?, pageModel: PageModelProtocol?, moduleUpdates: [String : Any]?) in
|
||||||
|
guard let self = self else { return }
|
||||||
|
if let pageUpdates, pageModel != nil {
|
||||||
|
self.loadObject?.pageJSON = pageUpdates
|
||||||
|
}
|
||||||
|
let mergedModuleUpdates = (loadObject?.modulesJSON ?? [:]).mergingLeft(moduleUpdates ?? [:])
|
||||||
|
self.loadObject?.modulesJSON = mergedModuleUpdates
|
||||||
|
self.debugLog("Applying async update page model \(pageModel.debugDescription) and modules \(mergedModuleUpdates.keys) to page.")
|
||||||
|
self.handleNewData(pageModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open func stopObservingForResponseJSONUpdates() {
|
open func stopObservingForResponseJSONUpdates() {
|
||||||
@ -77,6 +97,31 @@ import MVMCore
|
|||||||
self.observingForResponses = nil
|
self.observingForResponses = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pullUpdates(from notification: Notification) -> (pageUpdates: [String : Any]?, pageModel: PageModelProtocol?, moduleUpdates: [String : Any]?)? {
|
||||||
|
// Get the page data.
|
||||||
|
let pageUpdates = extractInterestedPageType(from: notification.userInfo?.optionalDictionaryForKey(KeyPageMap) ?? [:])
|
||||||
|
// Convert the page data into a new model.
|
||||||
|
var pageModel: PageModelProtocol? = nil
|
||||||
|
if let pageUpdates {
|
||||||
|
do {
|
||||||
|
// TODO: Rewiring to parse from plain JSON rather than this protocol indirection.
|
||||||
|
pageModel = try (self as? any TemplateProtocol & PageBehaviorHandlerProtocol & MVMCoreViewControllerProtocol)?.parseTemplate(pageJSON: pageUpdates)
|
||||||
|
} catch {
|
||||||
|
if let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: "updateJSON for pageType: \(String(describing: self.pageType))") {
|
||||||
|
MVMCoreLoggingHandler.shared()?.addError(toLog: coreError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get the module data.
|
||||||
|
let moduleUpdates = extractInterestedModules(from: notification.userInfo?.optionalDictionaryForKey(KeyModuleMap) ?? [:])
|
||||||
|
debugLog("Receiving page \(pageModel?.pageType ?? "none") & \(moduleUpdates?.keys.description ?? "none") modules from \((notification.userInfo?["MVMCoreLoadObject"] as? MVMCoreLoadObject)?.requestParameters?.url?.absoluteString ?? "")")
|
||||||
|
|
||||||
|
guard (pageUpdates != nil && pageModel != nil) || (moduleUpdates != nil && moduleUpdates!.count > 0) else { return nil }
|
||||||
|
|
||||||
|
// Bundle the transformations.
|
||||||
|
return (pageUpdates, pageModel, moduleUpdates)
|
||||||
|
}
|
||||||
|
|
||||||
open func pagesToListenFor() -> [String]? {
|
open func pagesToListenFor() -> [String]? {
|
||||||
guard let pageType = loadObject?.pageType else { return nil }
|
guard let pageType = loadObject?.pageType else { return nil }
|
||||||
return [pageType]
|
return [pageType]
|
||||||
@ -88,51 +133,22 @@ import MVMCore
|
|||||||
return requestModules + behaviorModules
|
return requestModules + behaviorModules
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc open func responseJSONUpdated(notification: Notification) {
|
private func extractInterestedPageType(from pageMap: [String: Any]) -> [String: Any]? {
|
||||||
// Checks for a page we are listening for.
|
guard let pageType = pagesToListenFor()?.first(where: { pageTypeListened -> Bool in
|
||||||
var hasDataUpdate = false
|
guard let page = pageMap.optionalDictionaryForKey(pageTypeListened),
|
||||||
var pageModel: PageModelProtocol? = nil
|
|
||||||
if let pagesLoaded = notification.userInfo?.optionalDictionaryForKey(KeyPageMap),
|
|
||||||
let loadObject,
|
|
||||||
let pageType = pagesToListenFor()?.first(where: { (pageTypeListened) -> Bool in
|
|
||||||
guard let page = pagesLoaded.optionalDictionaryForKey(pageTypeListened),
|
|
||||||
let pageType = page.optionalStringForKey(KeyPageType),
|
let pageType = page.optionalStringForKey(KeyPageType),
|
||||||
pageType == pageTypeListened
|
pageType == pageTypeListened
|
||||||
else { return false }
|
else { return false }
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}) {
|
}) else { return nil }
|
||||||
hasDataUpdate = true
|
return pageMap.optionalDictionaryForKey(pageType)
|
||||||
loadObject.pageJSON = pagesLoaded.optionalDictionaryForKey(pageType)
|
}
|
||||||
|
|
||||||
// Separate page updates from the module updates to avoid unecessary resets to behaviors and full re-renders.
|
private func extractInterestedModules(from moduleMap: [String: Any]) -> [String: Any]? {
|
||||||
do {
|
guard let modulesListened = modulesToListenFor() else { return nil }
|
||||||
pageModel = try parsePageJSON(loadObject: loadObject)
|
return moduleMap.filter { (key: String, value: Any) in
|
||||||
} catch {
|
modulesListened.contains { $0 == key }
|
||||||
if let coreError = MVMCoreErrorObject.createErrorObject(for: error, location: "updateJSON for pageType: \(String(describing: pageType))") {
|
|
||||||
MVMCoreLoggingHandler.shared()?.addError(toLog: coreError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks for modules we are listening for.
|
|
||||||
if let modulesLoaded = notification.userInfo?.optionalDictionaryForKey(KeyModuleMap),
|
|
||||||
let modulesListened = modulesToListenFor() {
|
|
||||||
for moduleName in modulesListened {
|
|
||||||
if let module = modulesLoaded.optionalDictionaryForKey(moduleName) {
|
|
||||||
hasDataUpdate = true
|
|
||||||
var currentModules = loadObject?.modulesJSON ?? [:]
|
|
||||||
currentModules.updateValue(module, forKey: moduleName)
|
|
||||||
loadObject?.modulesJSON = currentModules
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guard hasDataUpdate else { return }
|
|
||||||
|
|
||||||
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
|
||||||
self.handleNewData(pageModel)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>) -> Bool {
|
open func shouldFinishProcessingLoad(_ loadObject: MVMCoreLoadObject, error: AutoreleasingUnsafeMutablePointer<MVMCoreErrorObject?>) -> Bool {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user