(on target: P, keyPath: ReferenceWritableKeyPath, replacementMolecule: MoleculeModelProtocol) -> Bool {
+ if let currentMolecule = target[keyPath: keyPath], currentMolecule.id == replacementMolecule.id, let newHeadline = replacementMolecule as? T {
+ target[keyPath: keyPath] = newHeadline
+ return true
+ }
+ return false
+ }
+
+ func replaceChildMolecule(on target: P, keyPath: ReferenceWritableKeyPath, replacementMolecule: MoleculeModelProtocol) -> Bool {
+ if target[keyPath: keyPath].id == replacementMolecule.id, let newHeadline = replacementMolecule as? T {
+ target[keyPath: keyPath] = newHeadline
+ return true
+ }
+ return false
+ }
}
diff --git a/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift b/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift
index 531a48d1..2cd233b0 100644
--- a/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift
+++ b/MVMCoreUI/Atomic/Protocols/ModelProtocols/TemplateModelProtocol.swift
@@ -30,7 +30,7 @@ public extension TemplateModelProtocol {
return rootMolecules.reduceDepthFirstTraverse(options: options, depth: depth, initialResult: initialResult, nextPartialResult: nextPartialResult)
}
- func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol) -> Void) {
+ func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol, inout Bool) -> Void) {
return rootMolecules.depthFirstTraverse(options: options, depth: depth, onVisit: onVisit)
}
}
diff --git a/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift b/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift
index d10a2d19..a2da7cf4 100644
--- a/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift
+++ b/MVMCoreUI/Atomic/Protocols/MoleculeTreeTraversalProtocol.swift
@@ -19,7 +19,7 @@ public protocol MoleculeTreeTraversalProtocol {
func reduceDepthFirstTraverse(options: TreeTraversalOptions, depth: Int, initialResult: Result, nextPartialResult: (Result, MoleculeModelProtocol, Int)->Result) -> Result
- func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol)->Void)
+ func depthFirstTraverse(options: TreeTraversalOptions, depth: Int, onVisit: (Int, MoleculeModelProtocol, inout Bool)->Void)
//func breadthFirstTraverse()
}
@@ -37,7 +37,7 @@ public extension MoleculeTreeTraversalProtocol {
}
func printMolecules(options: TreeTraversalOptions = .parentFirst) {
- depthFirstTraverse(options: options, depth: 1) { (depth, molecule) in
+ depthFirstTraverse(options: options, depth: 1) { depth, molecule, stop in
print("\(String(repeating: ">>", count: depth)) \"\(molecule.moleculeName)\" [\(molecule)]")
}
}
@@ -50,4 +50,11 @@ public extension MoleculeTreeTraversalProtocol {
return accumulator
}
}
+
+ func replaceMolecule(with replacementMolecule: MoleculeModelProtocol) {
+ depthFirstTraverse(options: .parentFirst, depth: 0) { depth, molecule, stop in
+ guard let parentMolecule = molecule as? ParentMoleculeModelProtocol else { return }
+ stop = parentMolecule.replaceChildMolecule(with: replacementMolecule)
+ }
+ }
}
diff --git a/MVMCoreUI/Categories/UIColor+MFConvenience.h b/MVMCoreUI/Categories/UIColor+MFConvenience.h
index 97280ba1..10e9433d 100644
--- a/MVMCoreUI/Categories/UIColor+MFConvenience.h
+++ b/MVMCoreUI/Categories/UIColor+MFConvenience.h
@@ -183,8 +183,12 @@
// Returns a gradient lighter color;
+ (nonnull UIColor *)mfGradientColor:(nullable UIColor *)color;
+// Returns if the color is dark or not
+- (BOOL)isDark;
+
#pragma mark - Hex String
+ (nullable NSString *)hexStringForColor:(nonnull UIColor*)color;
+ (nonnull UIColor *)mfGetColorForHexWithTransparency:(nonnull NSString *)hexString;
+
@end
diff --git a/MVMCoreUI/Categories/UIColor+MFConvenience.m b/MVMCoreUI/Categories/UIColor+MFConvenience.m
index 8786df01..cd809509 100644
--- a/MVMCoreUI/Categories/UIColor+MFConvenience.m
+++ b/MVMCoreUI/Categories/UIColor+MFConvenience.m
@@ -389,6 +389,12 @@
return [UIColor whiteColor];
}
+- (BOOL)isDark {
+ CGFloat greyScale = 0;
+ [self getWhite:&greyScale alpha:nil];
+ return greyScale < 0.5;
+}
+
#pragma mark - Hex String
+ (nullable NSString *)hexStringForColor:(nonnull UIColor*)color {
diff --git a/MVMCoreUI/Containers/NavigationController/NavigationController.swift b/MVMCoreUI/Containers/NavigationController/NavigationController.swift
index eb43201a..8f72a792 100644
--- a/MVMCoreUI/Containers/NavigationController/NavigationController.swift
+++ b/MVMCoreUI/Containers/NavigationController/NavigationController.swift
@@ -7,9 +7,12 @@
//
import UIKit
+import MVMCore
+import Combine
@objcMembers open class NavigationController: UINavigationController, MVMCoreViewManagerViewControllerProtocol {
public weak var manager: (UIViewController & MVMCoreViewManagerProtocol)?
+ private var cancellables: Set = []
/// Getter for the main navigation controller
public static func navigationController() -> Self? {
@@ -20,9 +23,9 @@ import UIKit
public static func setupNavigationController() -> Self? {
let navigationController = self.init()
MVMCoreUISession.sharedGlobal()?.navigationController = navigationController
- MVMCoreNavigationHandler.shared()?.viewControllerToPresentOn = navigationController
- MVMCoreNavigationHandler.shared()?.navigationController = navigationController
- MVMCoreNavigationHandler.shared()?.addDelegate(navigationController)
+ NavigationHandler.shared().viewControllerToPresentOn = navigationController
+ NavigationHandler.shared().navigationController = navigationController
+ navigationController.subscribe()
navigationController.setNavigationBarUI(with: NavigationItemModel())
return navigationController
}
@@ -34,15 +37,45 @@ import UIKit
return navigationController
}
- /// Convenience function to return the navigation model of the lowest controller traversing managers if applicable.
+ /** Subscribes for events.
+ Updates the navigation item of the new view controller when one is pushed.
+ Based on ``NavigationItemModelProtocol`` of ``PageProtocol/pageModel``. Traverses the manager for the view controller if necessary.
+ */
+ @MainActor
+ public func subscribe() {
+ NavigationHandler.shared().onNavigation.sink { [weak self] (event, operation) in
+ guard let self = self,
+ self == operation.navigationType.getNavigationController(),
+ let viewController = NavigationHandler.shared().getViewControllers(for: self).last,
+ let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) else { return }
+ switch event {
+ case .willNavigate:
+ if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) {
+ MVMCoreViewManagerViewControllerProtocolHelper.helpSetManager(self, viewController: controller)
+ }
+ if let model = getNavigationModel(from: newViewController) {
+ self.setNavigationItem(with: model, for: viewController)
+ self.setNavigationBarUI(with: model)
+ }
+ self.manager?.willDisplay?(newViewController)
+ case .didNavigate:
+ self.manager?.displayedViewController?(newViewController)
+ if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) {
+ controller.viewControllerReady?(inManager: self)
+ }
+ @unknown default: break
+ }
+ }.store(in: &cancellables)
+ }
+
+ /// Convenience function to return the navigation model of the view controller.
public func getNavigationModel(from viewController: UIViewController) -> NavigationItemModelProtocol? {
return (viewController as? PageProtocol)?.pageModel?.navigationBar
}
/// Verifies the controller is the currently displayed controller.
public func isDisplayed(viewController: UIViewController) -> Bool {
- guard let topViewController = topViewController,
- viewController == MVMCoreUIUtility.getViewControllerTraversingManagers(topViewController) else {
+ guard viewController == getViewController() else {
return false
}
return true
@@ -84,8 +117,10 @@ extension NavigationController: MVMCoreViewManagerProtocol {
manager?.willDisplay?(viewController)
}
+ /// Updates the navigation item/bar of the current view controller based on the passed in model and view controller.
+ @MainActor
private func updateNavigationView(with model: NavigationItemModelProtocol, for viewController: UIViewController) {
- guard let topViewController = topViewController else { return }
+ guard let topViewController = NavigationHandler.shared().getViewControllers(for: self).last else { return }
setNavigationItem(with: model, for: topViewController, coordinatingWith: viewController as? PageBehaviorHandlerProtocol)
setNavigationBarUI(with: model)
@@ -99,34 +134,6 @@ extension NavigationController: MVMCoreViewManagerProtocol {
}
}
-extension NavigationController: MVMCorePresentationDelegateProtocol {
- public func navigationController(_ navigationController: UINavigationController, prepareDisplayFor viewController: UIViewController) {
- if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) {
- MVMCoreViewManagerViewControllerProtocolHelper.helpSetManager(self, viewController: controller)
- }
- guard self == navigationController,
- let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController),
- let model = getNavigationModel(from: newViewController) else { return }
- setNavigationItem(with: model, for: viewController)
- setNavigationBarUI(with: model)
- }
-
- public func navigationController(_ navigationController: UINavigationController, willDisplay viewController: UIViewController) {
- guard self == navigationController,
- let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) else { return }
- manager?.willDisplay?(newViewController)
- }
-
- public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) {
- guard self == navigationController,
- let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) else { return }
- manager?.displayedViewController?(newViewController)
- if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) {
- controller.viewControllerReady?(inManager: self)
- }
- }
-}
-
extension UIColor {
func image(_ size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {
return UIGraphicsImageRenderer(size: size).image { rendererContext in
diff --git a/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift b/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift
index e1d3a247..3372c1ab 100644
--- a/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift
+++ b/MVMCoreUI/Containers/NavigationController/UINavigationController+Extension.swift
@@ -7,10 +7,12 @@
//
import Foundation
+import MVMCore
public extension UINavigationController {
/// Convenience function for setting the navigation item.
+ @MainActor
func setNavigationItem(with model: NavigationItemModelProtocol, for viewController: UIViewController, coordinatingWith pageBehaviorController: PageBehaviorHandlerProtocol? = nil) {
let behaviorHandler = pageBehaviorController ?? viewController as? PageBehaviorHandlerProtocol;
@@ -32,13 +34,14 @@ public extension UINavigationController {
}
/// Convenience function for setting the navigation buttons.
+ @MainActor
func setNavigationButtons(with model: NavigationItemModelProtocol, for viewController: UIViewController) {
let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject
var leftItems: [UIBarButtonItem] = []
if model.hidesSystemBackButton,
model.alwaysShowBackButton != false {
if let backButtonModel = model.backButton,
- MVMCoreNavigationHandler.shared()?.getViewControllers(for: self)?.count ?? 0 > 1 || model.alwaysShowBackButton ?? false {
+ NavigationHandler.shared().getViewControllers(for: self).count > 1 || model.alwaysShowBackButton ?? false {
leftItems.append(backButtonModel.createNavigationItemButton(delegateObject: delegate, additionalData: nil))
}
if let leftItemModels = model.additionalLeftButtons {
@@ -59,6 +62,7 @@ public extension UINavigationController {
}
/// Convenience function for setting the navigation titleView.
+ @MainActor
func setNavigationTitleView(with model: NavigationItemModelProtocol, for viewController: UIViewController, coordinatingWith pageBehaviorController: PageBehaviorHandlerProtocol? = nil) {
guard let titleViewModel = model.titleView else { return }
@@ -86,6 +90,7 @@ public extension UINavigationController {
}
/// Convenience function for setting the navigation bar ui
+ @MainActor
func setNavigationBarUI(with model: NavigationItemModelProtocol) {
let navigationBar = navigationBar
let font = Styler.Font.BoldTitleSmall.getFont(false)
@@ -113,4 +118,11 @@ public extension UINavigationController {
setNavigationBarHidden(model.hidden, animated: true)
}
+
+ @MainActor
+ func getViewController() -> UIViewController? {
+ guard let topViewController = getViewControllers().last,
+ let viewController = MVMCoreUIUtility.getViewControllerTraversingManagers(topViewController) else { return nil }
+ return viewController
+ }
}
diff --git a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift
index 8b509827..20f4449c 100644
--- a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift
+++ b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController+Extension.swift
@@ -20,7 +20,8 @@ public protocol StatusBarUI {
// Navigation bar update functions
public extension MVMCoreUISplitViewController {
- /// Updates the state for various controls (navigation, tab, progress) for the controller.
+ /// Updates the state for various controls (top navigation controller item, tab, progress) for the controller.
+ @MainActor
func updateState(with viewController: UIViewController) {
guard let navigationController = navigationController,
navigationController.isDisplayed(viewController: viewController) else { return }
@@ -30,7 +31,9 @@ public extension MVMCoreUISplitViewController {
}
// MARK: - Progress Bar
- /// Updates the progress bar based on the page json for the view controller.
+ /** Updates the progress bar based on the page json for the view controller.
+ Uses a string value between 0 and 100 from key progressPercent in the MVMCoreViewControllerProtocol.loadObject.pageJSON.
+ */
func updateProgressBar(for viewController: UIViewController) {
guard let viewController = viewController as? MVMCoreViewControllerProtocol else { return }
var progress: Float = 0.0
@@ -42,12 +45,16 @@ public extension MVMCoreUISplitViewController {
}
// MARK: - Tab Bar
- /// Updates the tab bar based on the page json for the view controller.
+ /** Updates the tab bar based on the page json for the view controller.
+ For the index: checks the view controller's pageModel (``PageProtocol``) property ``TabPageModelProtocol/tabBarIndex``, else tabBarIndex in action map that led to this page, else the previous tab bar index of this page.
+ For hidden: checks the view controller's pageModel (``PageProtocol``) property ``TabPageModelProtocol/tabBarHidden``, else tabBarHidden in action map that led to this page, else it is visibile.
+ */
+ @MainActor
func updateTabBar(for viewController: UIViewController) {
let mvmViewController = viewController as? MVMCoreViewControllerProtocol
tabBar?.delegateObject = mvmViewController?.delegateObject?() as? MVMCoreUIDelegateObject
- let navigationIndex = (MVMCoreNavigationHandler.shared()?.getViewControllers(for: navigationController)?.count ?? 1) - 1
+ let navigationIndex = (navigationController != nil ? NavigationHandler.shared().getViewControllers(for: navigationController!).count : 1) - 1
// Set the highlighted index. In terms of priority, Page > Action > Previous.
if let index = ((viewController as? PageProtocol)?.pageModel as? TabPageModelProtocol)?.tabBarIndex {
@@ -80,10 +87,13 @@ public extension MVMCoreUISplitViewController {
}
// MARK: - Navigation Bar
- /// Convenience function. Sets the navigation and split view properties for the view controller. Panel access is determined if view controller is a detail view protocol.
+ /** Convenience function. Sets the navigation and split view properties for the view controller.
+ Panel access is determined if view controller is a ``MVMCoreUIDetailViewProtocol``
+ */
+ @MainActor
func setNavigationBar(for viewController: UIViewController, navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol) {
guard navigationController == self.navigationController,
- viewController == getCurrentDetailViewController() else {
+ self.navigationController?.isDisplayed(viewController: viewController) == true else {
/// Not the split view navigation controller, skip split functions.
return
}
@@ -97,9 +107,13 @@ public extension MVMCoreUISplitViewController {
setNavigationIconColor(navigationItemModel.tintColor.uiColor)
}
- /// Sets the left navigation items for the view controller based on model and splitview.
+ /** Sets the left navigation items for the top view controller based on the model and viewController.
+ Panel access is determined if view controller is a ``MVMCoreUIDetailViewProtocol``
+ */
+ @MainActor
func setLeftNavigationButtons(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol?, viewController: UIViewController) {
- guard let topViewController = navigationController.topViewController else { return }
+ let viewControllers = NavigationHandler.shared().getViewControllers(for: navigationController)
+ guard let topViewController = viewControllers.last else { return }
var leftItems: [UIBarButtonItem] = []
let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject
@@ -110,7 +124,7 @@ public extension MVMCoreUISplitViewController {
if let forceBackButton = navigationItemModel?.alwaysShowBackButton {
showBackButton = forceBackButton
} else {
- showBackButton = MVMCoreNavigationHandler.shared()?.getViewControllers(for: navigationController)?.count ?? 0 > 1
+ showBackButton = viewControllers.count > 1
}
if showBackButton {
if let backButtonModel = navigationItemModel?.backButton {
@@ -144,9 +158,12 @@ public extension MVMCoreUISplitViewController {
topViewController.navigationItem.setLeftBarButtonItems(leftItems.count > 0 ? leftItems : nil, animated: !DisableAnimations.boolValue)
}
- /// Sets the right navigation items for the view controller based on model and splitview.
+ /** Sets the right navigation items for the top view controller based on the model and viewController.
+ Panel access is determined if view controller is a ``MVMCoreUIDetailViewProtocol``
+ */
+ @MainActor
func setRightNavigationButtons(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol?, viewController: UIViewController) {
- guard let topViewController = navigationController.topViewController else { return }
+ guard let topViewController = NavigationHandler.shared().getViewControllers(for: navigationController).last else { return }
let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject
var rightItems: [UIBarButtonItem] = []
@@ -173,6 +190,9 @@ public extension MVMCoreUISplitViewController {
topViewController.navigationItem.setRightBarButtonItems(rightItems.count > 0 ? rightItems : nil, animated: !DisableAnimations.boolValue)
}
+ /** If the current detail view controller has a navigation model.
+ ``NavigationController/getNavigationModel(from:)`` from ``getCurrentDetailViewController()``
+ */
@objc func navigationBarModelExists() -> Bool {
// Legacy Navigation
guard let currentViewController = getCurrentDetailViewController(),
@@ -180,8 +200,9 @@ public extension MVMCoreUISplitViewController {
return true
}
- /// Convenience function to update the navigation bar if the controller is the current lowest controller.
- @objc func updateNavigationBarFor(viewController: UIViewController) {
+ /// Convenience function to update the navigation bar if the controller is the current detail controller.
+ @MainActor @objc
+ func updateNavigationBarFor(viewController: UIViewController) {
guard let navigationController = navigationController,
navigationController.isDisplayed(viewController: viewController),
let model = navigationController.getNavigationModel(from: viewController) else { return }
@@ -201,9 +222,13 @@ public extension MVMCoreUISplitViewController {
return .default
}
- /// Updates the status bar background color and style.
- @objc func setStatusBarForCurrentViewController() {
- let viewController = getCurrentViewController() as? MVMCoreUIDetailViewProtocol
+ /** Updates the status bar background color and style for the passed view controller.
+ The background color is fetched from ``MVMCoreUIDetailViewProtocol/defaultStatusBarBackgroundColor()``, else the current navigation bar background, else the current status bar background color.
+ The backgroundStytle is fetched from ``MVMCoreUIDetailViewProtocol/defaultStatusBarStyle()`` else ``getStatusBarStyle(for:)``
+ */
+ @MainActor
+ func setStatusBar(for viewController: UIViewController?) {
+ let viewController = viewController as? MVMCoreUIDetailViewProtocol
let backgroundColor = viewController?.defaultStatusBarBackgroundColor?() ??
navigationController?.navigationBar.standardAppearance.backgroundColor ??
statusBarView?.backgroundColor
@@ -213,6 +238,14 @@ public extension MVMCoreUISplitViewController {
setStatusBarBackgroundColor(backgroundColor, style: style)
}
+
+ /** Updates the status bar background color and style for the current view controller.
+ See ``setStatusBar(for:)``
+ */
+ @MainActor
+ @objc func setStatusBarForCurrentViewController() {
+ setStatusBar(for: getCurrentViewController())
+ }
}
extension MVMCoreUISplitViewController: MVMCoreViewManagerProtocol {
@@ -235,6 +268,17 @@ extension MVMCoreUISplitViewController: MVMCoreViewManagerProtocol {
}
@objc public extension MVMCoreUISplitViewController {
+ @objc func goBack() {
+ Task(priority: .userInitiated) { @MainActor in
+ if let viewController = getCurrentDetailViewController() as? MVMCoreUIDetailViewProtocol,
+ let backButtonPressed = viewController.backButtonPressed {
+ backButtonPressed()
+ } else {
+ await NavigationHandler.shared().popTopViewController()
+ }
+ }
+ }
+
/// Subscribes for notification events.
@objc func subscribeForNotifications() {
guard cancellables == nil else { return }
diff --git a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m
index 045dd0bf..c43d17d9 100644
--- a/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m
+++ b/MVMCoreUI/Containers/SplitViewController/MVMCoreUISplitViewController.m
@@ -7,7 +7,6 @@
//
#import "MVMCoreUISplitViewController.h"
-@import MVMCore.MVMCoreNavigationHandler;
@import MVMCore.MVMCoreDispatchUtility;
@import MVMCore.MVMCoreViewManagerProtocol;
@import MVMCore.MVMCoreActionUtility;
@@ -192,14 +191,7 @@ CGFloat const PanelAnimationDuration = 0.2;
}
- (IBAction)backButtonPressed:(id)sender {
- [MVMCoreDispatchUtility performBlockOnMainThread:^{
- UIViewController *detailViewController = [self getCurrentDetailViewController];
- if ([detailViewController conformsToProtocol:@protocol(MVMCoreUIDetailViewProtocol)] && [detailViewController respondsToSelector:@selector(backButtonPressed)]) {
- [((UIViewController *)detailViewController) backButtonPressed];
- } else {
- [[MVMCoreNavigationHandler sharedNavigationHandler] popTopViewControllerAnimated:YES];
- }
- }];
+ [self goBack];
}
- (IBAction)rightPanelButtonPressed:(id)sender {
@@ -258,7 +250,7 @@ CGFloat const PanelAnimationDuration = 0.2;
- (void)setLeftNavigationItemForViewController:(UIViewController * _Nonnull)viewController accessible:(BOOL)accessible extended:(BOOL)extended {
NSMutableArray *leftBarButtonItems = [NSMutableArray array];
- if (self.navigationController && [MVMCoreNavigationHandler.sharedNavigationHandler getViewControllersForNavigationController:self.navigationController].count > 1) {
+ if (self.navigationController && [self.navigationController getViewControllers].count > 1) {
[leftBarButtonItems addObject:self.backButton];
}
if ((accessible && !extended) && self.leftPanelButton) {
@@ -1074,7 +1066,7 @@ CGFloat const PanelAnimationDuration = 0.2;
// Returns the desired view or falls back. Hot fix until we can get away from using these functions...
+ (CGRect)getBounds:(UIView *)desiredView {
- UIView *view = desiredView ?: [MVMCoreNavigationHandler sharedNavigationHandler].navigationController.view ?: [MVMCoreGetterUtility getKeyWindow].rootViewController.view;
+ UIView *view = desiredView ?: [self mainSplitViewController].navigationController.view ?: [MVMCoreGetterUtility getKeyWindow].rootViewController.view;
return view ? view.bounds : [UIScreen mainScreen].bounds;
}
@@ -1099,17 +1091,7 @@ CGFloat const PanelAnimationDuration = 0.2;
}
- (UIViewController *)getCurrentVisibleController {
- UIViewController *baseViewController = [MVMCoreNavigationHandler sharedNavigationHandler].viewControllerToPresentOn ?: [MVMCoreGetterUtility getKeyWindow].rootViewController;
- UIViewController *viewController = nil;
- while (baseViewController.presentedViewController && !baseViewController.presentedViewController.isBeingDismissed) {
- viewController = baseViewController.presentedViewController;
- baseViewController = viewController;
- }
- // if it is not presented viewcontroller, existing BAU logic will be working
- if (!viewController) {
- viewController = [MVMCoreUIUtility getViewControllerTraversingManagers:self.navigationController.topViewController];
- }
- return viewController;
+ return [MVMCoreUIUtility getCurrentVisibleController];
}
- (UIViewController *)getCurrentDetailViewController {
diff --git a/MVMCoreUI/Containers/Views/ContainerModel.swift b/MVMCoreUI/Containers/Views/ContainerModel.swift
index 731e9c95..b54590dd 100644
--- a/MVMCoreUI/Containers/Views/ContainerModel.swift
+++ b/MVMCoreUI/Containers/Views/ContainerModel.swift
@@ -13,6 +13,8 @@ open class ContainerModel: ContainerModelProtocol, Codable {
// MARK: - Properties
//--------------------------------------------------
+ public var id: String = UUID().uuidString
+
public var horizontalAlignment: UIStackView.Alignment?
public var useHorizontalMargins: Bool?
public var leftPadding: CGFloat?
@@ -30,6 +32,7 @@ open class ContainerModel: ContainerModelProtocol, Codable {
//--------------------------------------------------
private enum CodingKeys: String, CodingKey {
+ case id
case horizontalAlignment
case useHorizontalMargins
case leftPadding
@@ -74,6 +77,9 @@ open class ContainerModel: ContainerModelProtocol, Codable {
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
+
+ id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
+
if let verticalAlignmentString = try typeContainer.decodeIfPresent(String.self, forKey: .verticalAlignment) {
verticalAlignment = ContainerHelper.getAlignment(for: verticalAlignmentString)
}
@@ -92,6 +98,7 @@ open class ContainerModel: ContainerModelProtocol, Codable {
open func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
+ try container.encode(id, forKey: .id)
try container.encodeIfPresent(ContainerHelper.getAlignmentString(for: verticalAlignment), forKey: .verticalAlignment)
try container.encodeIfPresent(ContainerHelper.getAlignmentString(for: horizontalAlignment), forKey: .horizontalAlignment)
try container.encodeIfPresent(useHorizontalMargins, forKey: .useHorizontalMargins)
diff --git a/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift b/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift
index db339cf8..458c0169 100644
--- a/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift
+++ b/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift
@@ -205,7 +205,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
}
}
- public func navigationController(_ navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
+ public func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
// Only percent interact if we've already loaded the view controller
guard let customInteractor = customInteractor,
let index = index,
@@ -229,7 +229,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
hideNavigationBarLine(true)
}
- public func navigationController(_ navigationController: UINavigationController, willDisplay viewController: UIViewController) {
+ public func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
guard navigationController == subNavigationController else { return }
if let viewController = viewController as? UIViewController & MVMCoreViewManagerViewControllerProtocol & MVMCoreViewControllerProtocol {
@@ -247,7 +247,7 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
commitTo(controller: viewController)
}
- public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) {
+ public func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
guard navigationController == subNavigationController else { return }
// Need to track swipe action.
if needToTrackTabSelect {
@@ -272,7 +272,9 @@ open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol,
if let controller = viewControllers[indexPath.row] {
// Load controller from the cache
needToTrackTabSelect = true
- MVMCoreNavigationHandler.shared()?.replaceTopViewController(with: controller, navigationController: subNavigationController, animated: true, delegate: self, replaceInStack: false, completionHandler: nil)
+ Task(priority: .userInitiated) {
+ await NavigationHandler.shared().replace(viewController: controller, navigationController:subNavigationController, delegateObject:delegateObject(), tryToReplace: false, animated: true)
+ }
} else if let tabsModel = tabs.tabsModel,
let action = tabsModel.tabs[indexPath.row].action {
// Perform the tab action
diff --git a/MVMCoreUI/Notification/NotificationHandler.swift b/MVMCoreUI/Notification/NotificationHandler.swift
index ff9d90ad..9ecc2efb 100644
--- a/MVMCoreUI/Notification/NotificationHandler.swift
+++ b/MVMCoreUI/Notification/NotificationHandler.swift
@@ -268,7 +268,7 @@ public class NotificationOperation: MVMCoreOperation {
}
})!
transitionOperation.completionBlock = completionBlock
- MVMCoreNavigationHandler.shared()?.addNavigationOperation(transitionOperation)
+ NavigationHandler.shared().navigationQueue.addOperation(transitionOperation)
return transitionOperation
}
@@ -314,6 +314,8 @@ open class NotificationHandler {
private var transitionDelegate: NotificationTransitionDelegateProtocol
+ private var cancellable: Cancellable?
+
// MARK: - Publishers
/// Publishes when a notification will show.
@@ -354,7 +356,27 @@ open class NotificationHandler {
/// Registers to know when pages change.
private func registerForPageChanges() {
- MVMCoreNavigationHandler.shared()?.addDelegate(self)
+ cancellable = NavigationHandler.shared().onNavigation
+ .filter({ $0.0 == .didNavigate })
+ .sink { [weak self] (event, operation) in
+ guard let self = self else { return }
+ Task {
+ // Update displayable for each top alert operation when page type changes, in top queue priority order.
+ guard self.queue.operations.count > 0,
+ let navigationController = await operation.navigationType.getNavigationController(),
+ await navigationController == MVMCoreUISplitViewController.main()?.navigationController,
+ let viewController = await navigationController.getViewController() else { return }
+ let pageType = (viewController as? MVMCoreViewControllerProtocol)?.pageType
+ self.queue.operations.compactMap {
+ $0 as? NotificationOperation
+ }.sorted {
+ $0.notificationModel.priority.rawValue > $1.notificationModel.priority.rawValue
+ }.forEach {
+ $0.updateDisplayable(by: pageType)
+ }
+ self.reevaluteQueue()
+ }
+ }
}
/// Checks for new top alert json
@@ -534,26 +556,6 @@ open class NotificationHandler {
}
}
-extension NotificationHandler: MVMCorePresentationDelegateProtocol {
- // Update displayable for each top alert operation when page type changes, in top queue priority order.
- public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) {
- guard queue.operations.count > 0 else { return }
- let viewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController)
- guard viewController == MVMCoreUISplitViewController.main()?.getCurrentViewController() else { return }
- let pageType = (viewController as? MVMCoreViewControllerProtocol)?.pageType
- Task {
- queue.operations.compactMap {
- $0 as? NotificationOperation
- }.sorted {
- $0.notificationModel.priority.rawValue > $1.notificationModel.priority.rawValue
- }.forEach {
- $0.updateDisplayable(by: pageType)
- }
- reevaluteQueue()
- }
- }
-}
-
extension NotificationOperation {
/// Updates the operation and notification with the new model.
public func update(with model: NotificationModel, delegateObject: MVMCoreUIDelegateObject?) {
@@ -562,7 +564,9 @@ extension NotificationOperation {
guard isExecuting,
!isCancelled else { return }
self.log(message: "Operation Updated")
- updateStopTimer()
+ displayableQueue.async(flags: .barrier) { [self] in
+ updateStopTimer()
+ }
Task {
await transitionDelegate.update(with: notificationModel, delegateObject: delegateObject)
NotificationHandler.shared()?.onNotificationUpdated.send((notification, notificationModel))
diff --git a/MVMCoreUI/OtherHandlers/CoreUIObject.swift b/MVMCoreUI/OtherHandlers/CoreUIObject.swift
index b96e9b93..d1fd18f9 100644
--- a/MVMCoreUI/OtherHandlers/CoreUIObject.swift
+++ b/MVMCoreUI/OtherHandlers/CoreUIObject.swift
@@ -9,19 +9,22 @@
import UIKit
import MVMCore
-@objcMembers open class CoreUIObject: MVMCoreObject {
+@objcMembers
+public class CoreUIObject: NSObject {
+ private static var singleton = CoreUIObject()
+ public static func sharedInstance() -> CoreUIObject? { singleton }
+ private override init() {}
+
public var alertHandler: AlertHandler?
public var topNotificationHandler: NotificationHandler?
- open override func defaultInitialSetup() {
+ public func defaultInitialSetup() {
+ MVMCoreObject.sharedInstance()?.defaultInitialSetup()
CoreUIModelMapping.registerObjects()
- loadHandler = MVMCoreLoadHandler()
- cache = MVMCoreCache()
- session = MVMCoreUISession()
- sessionHandler = MVMCoreSessionTimeHandler()
- actionHandler = MVMCoreUIActionHandler()
- viewControllerMapping = MVMCoreUIViewControllerMappingObject()
- loggingDelegate = MVMCoreUILoggingHandler()
+ MVMCoreObject.sharedInstance()?.session = MVMCoreUISession()
+ MVMCoreObject.sharedInstance()?.actionHandler = MVMCoreUIActionHandler()
+ MVMCoreObject.sharedInstance()?.viewControllerMapping = MVMCoreUIViewControllerMappingObject()
+ MVMCoreObject.sharedInstance()?.loggingDelegate = MVMCoreUILoggingHandler()
alertHandler = AlertHandler()
}
}
diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.swift b/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.swift
index b2c3d42c..49a3d5e9 100644
--- a/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.swift
+++ b/MVMCoreUI/OtherHandlers/MVMCoreUIActionHandler.swift
@@ -57,6 +57,8 @@ import SafariServices
@MainActor
open func openURL(inSafariWebView url: URL) {
let safariViewController = SFSafariViewController(url: url)
- MVMCoreNavigationHandler.shared()?.present(safariViewController, animated: true)
+ Task(priority: .high) {
+ await NavigationHandler.shared().present(viewController: safariViewController)
+ }
}
}
diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUISession.m b/MVMCoreUI/OtherHandlers/MVMCoreUISession.m
index 42a3e769..3b1d5913 100644
--- a/MVMCoreUI/OtherHandlers/MVMCoreUISession.m
+++ b/MVMCoreUI/OtherHandlers/MVMCoreUISession.m
@@ -9,7 +9,8 @@
#import "MVMCoreUISession.h"
#import "MFLoadingViewController.h"
#import "NSLayoutConstraint+MFConvenience.h"
-@import MVMCore.MVMCoreObject;
+@import MVMCore.MVMCoreLoadingOverlayDelegateProtocol;
+@import MVMCore.Swift;
@interface MVMCoreUISession ()
diff --git a/MVMCoreUI/Utility/MVMCoreUIUtility+Extension.swift b/MVMCoreUI/Utility/MVMCoreUIUtility+Extension.swift
index ffb74ef9..e04de6bb 100644
--- a/MVMCoreUI/Utility/MVMCoreUIUtility+Extension.swift
+++ b/MVMCoreUI/Utility/MVMCoreUIUtility+Extension.swift
@@ -7,7 +7,7 @@
//
import UIKit
-
+import MVMCore
public extension MVMCoreUIUtility {
@@ -66,3 +66,25 @@ public extension MVMCoreUIUtility {
return nil
}
}
+
+@objc
+public extension MVMCoreUIUtility {
+ @objc @MainActor
+ static func getVisibleViewController() -> UIViewController? {
+ var viewController = NavigationHandler.shared().getViewControllerToPresentOn()
+ while let presentedController = viewController?.presentedViewController,
+ !presentedController.isBeingDismissed {
+ viewController = presentedController
+ }
+ if let navigationController = viewController as? UINavigationController {
+ viewController = navigationController.topViewController
+ }
+ if let viewController = viewController {
+ return getViewControllerTraversingManagers(viewController)
+ } else if let viewController = MVMCoreUISession.sharedGlobal()?.navigationController?.topViewController {
+ return getViewControllerTraversingManagers(viewController)
+ } else {
+ return nil
+ }
+ }
+}
diff --git a/MVMCoreUI/Utility/MVMCoreUIUtility.m b/MVMCoreUI/Utility/MVMCoreUIUtility.m
index 01a8c435..ad7366df 100644
--- a/MVMCoreUI/Utility/MVMCoreUIUtility.m
+++ b/MVMCoreUI/Utility/MVMCoreUIUtility.m
@@ -11,7 +11,6 @@
#import "MVMCoreUISession.h"
#import "MVMCoreUISplitViewController.h"
#import
-@import MVMCore.MVMCoreNavigationHandler;
@import MVMCore.MVMCoreGetterUtility;
@implementation MVMCoreUIUtility
@@ -52,19 +51,10 @@
}
+ (UIViewController *)getCurrentVisibleController {
- UIViewController *baseViewController = [MVMCoreNavigationHandler sharedNavigationHandler].viewControllerToPresentOn ?: [MVMCoreGetterUtility getKeyWindow].rootViewController;
- UIViewController *viewController = nil;
- while (baseViewController.presentedViewController && !baseViewController.presentedViewController.isBeingDismissed) {
- viewController = baseViewController.presentedViewController;
- baseViewController = viewController;
- }
- if ([viewController isKindOfClass:[UINavigationController class]]) {
- viewController = ((UINavigationController *)viewController).topViewController;
- }
- // if it is not presented viewcontroller, existing BAU logic will be working
- if (!viewController) {
- viewController = [self getViewControllerTraversingManagers:[MVMCoreUISession sharedGlobal].navigationController.topViewController];
- }
+ __block UIViewController *viewController = nil;
+ [MVMCoreDispatchUtility performSyncBlockOnMainThread:^{
+ viewController = [self getVisibleViewController];
+ }];
return viewController;
}