From 64535c6b2155f2fe72bb8810b12fdff61f03e347 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Mon, 21 Aug 2023 17:03:47 -0400 Subject: [PATCH] ONEAPP-5208: Swiftify the navigation handler --- MVMCore/MVMCore.xcodeproj/project.pbxproj | 4 + .../ActionHandling/ActionShareHandler.swift | 3 +- ...VMCoreLoadRequestOperation+Extension.swift | 91 +++++++++ .../MVMCoreLoadRequestOperation.h | 2 + .../MVMCoreLoadRequestOperation.m | 6 +- .../NavigationHandler.swift | 189 +++++------------- .../NavigationOperation.swift | 17 +- .../MVMCoreActionUtility+Extension.swift | 76 +++++++ .../Utility/Helpers/MVMCoreActionUtility.h | 4 - .../Utility/Helpers/MVMCoreActionUtility.m | 26 --- 10 files changed, 242 insertions(+), 176 deletions(-) create mode 100644 MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift diff --git a/MVMCore/MVMCore.xcodeproj/project.pbxproj b/MVMCore/MVMCore.xcodeproj/project.pbxproj index a627efe..bc589d2 100644 --- a/MVMCore/MVMCore.xcodeproj/project.pbxproj +++ b/MVMCore/MVMCore.xcodeproj/project.pbxproj @@ -97,6 +97,7 @@ AF60A7F4289212EB00919EEB /* MVMCoreError.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF60A7F3289212EB00919EEB /* MVMCoreError.swift */; }; AF686FDA2A8A876A008F666A /* NavigationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF686FD92A8A876A008F666A /* NavigationOperation.swift */; }; AF686FDC2A8A87EA008F666A /* KVOPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF686FDB2A8A87EA008F666A /* KVOPublisher.swift */; }; + AF686FDE2A8D29CD008F666A /* MVMCoreLoadRequestOperation+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF686FDD2A8D29CD008F666A /* MVMCoreLoadRequestOperation+Extension.swift */; }; AF69D4E9286E54D500BC6862 /* ActionCallHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4E8286E54D500BC6862 /* ActionCallHandler.swift */; }; AF69D4EB286E586200BC6862 /* ActionRestartHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4EA286E586200BC6862 /* ActionRestartHandler.swift */; }; AF69D4ED286E5D8C00BC6862 /* ActionCancelHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4EC286E5D8C00BC6862 /* ActionCancelHandler.swift */; }; @@ -252,6 +253,7 @@ AF60A7F3289212EB00919EEB /* MVMCoreError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreError.swift; sourceTree = ""; }; AF686FD92A8A876A008F666A /* NavigationOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationOperation.swift; sourceTree = ""; }; AF686FDB2A8A87EA008F666A /* KVOPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KVOPublisher.swift; sourceTree = ""; }; + AF686FDD2A8D29CD008F666A /* MVMCoreLoadRequestOperation+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreLoadRequestOperation+Extension.swift"; sourceTree = ""; }; AF69D4E8286E54D500BC6862 /* ActionCallHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCallHandler.swift; sourceTree = ""; }; AF69D4EA286E586200BC6862 /* ActionRestartHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRestartHandler.swift; sourceTree = ""; }; AF69D4EC286E5D8C00BC6862 /* ActionCancelHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCancelHandler.swift; sourceTree = ""; }; @@ -562,6 +564,7 @@ AFBB964B1FBA3A560008D868 /* MVMCoreLoadHandler.m */, AFBB964A1FBA3A560008D868 /* MVMCoreLoadRequestOperation.h */, AFBB96521FBA3A570008D868 /* MVMCoreLoadRequestOperation.m */, + AF686FDD2A8D29CD008F666A /* MVMCoreLoadRequestOperation+Extension.swift */, AFBB96471FBA3A560008D868 /* MVMCoreLoadObject.h */, AFBB96481FBA3A560008D868 /* MVMCoreLoadObject.m */, AFBB96491FBA3A560008D868 /* MVMCoreRequestParameters.h */, @@ -935,6 +938,7 @@ AF69D4F1286E9D8000BC6862 /* ActionNoopHandler.swift in Sources */, 016FF6F2259A4FCC00F5E4AA /* ClientParameterModel.swift in Sources */, D2DEDCBB23C65BC300C44CC4 /* Percent.swift in Sources */, + AF686FDE2A8D29CD008F666A /* MVMCoreLoadRequestOperation+Extension.swift in Sources */, 60CBD0542A02397A00056CB0 /* MVMCoreSessionTimeHandler.swift in Sources */, AF686FDC2A8A87EA008F666A /* KVOPublisher.swift in Sources */, AFBB966A1FBA3A570008D868 /* MVMCoreLoadRequestOperation.m in Sources */, diff --git a/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift index c33d5b2..c0a83bc 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift @@ -7,6 +7,7 @@ // import Foundation + open class ActionShareHandler: MVMCoreActionHandlerProtocol { required public init() {} @@ -28,7 +29,7 @@ open class ActionShareHandler: MVMCoreActionHandlerProtocol { open func shareWith(activityItems: [Any], model: ActionShareModel, delegateObject: DelegateObject? = nil) async throws { try await withCheckedThrowingContinuation { continuation in let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) - controller.popoverPresentationController?.sourceView = MVMCoreNavigationHandler.shared()?.viewControllerToPresentOn?.view + controller.popoverPresentationController?.sourceView = NavigationHandler.shared().viewControllerToPresentOn?.view controller.completionWithItemsHandler = {(activityType: UIActivity.ActivityType?, completed: Bool, returnedItems: [Any]?, error: Error?) in if completed { // Activity was completed, considered finished. diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift new file mode 100644 index 0000000..2101774 --- /dev/null +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation+Extension.swift @@ -0,0 +1,91 @@ +// +// MVMCoreLoadRequestOperation+Extension.swift +// MVMCore +// +// Created by Scott Pfeil on 8/16/23. +// Copyright © 2023 myverizon. All rights reserved. +// + +import Foundation + +public enum PopBackError: MVMError, CustomStringConvertible { + case noPageType + case noNavigationHandler + case noViewController + + public var description: String { + switch self { + case .noPageType: + return "Cannot pop with a pageType." + case .noNavigationHandler: + return "Cannot pop with a navigation controller." + case .noViewController: + return "No View Controller of popBackPageType in the hierarchy" + } + } + + public var errorCode: Int { + switch self { + case .noPageType: + return 55 + case .noNavigationHandler: + return 56 + case .noViewController: + return 57 + } + } +} + +@objc +public extension MVMCoreLoadRequestOperation { + @objc + func popBackToPage(for loadObject: MVMCoreLoadObject) { + Task(priority: .high) { + do { + guard let popBackPageType = loadObject.pageJSON?.optionalStringForKey("popBackPageType") else { + throw PopBackError.noPageType + } + try await NavigationHandler.shared().popToViewController(with: popBackPageType, navigationController: loadObject.requestParameters?.navigationController, delegateObject: loadObject.delegateObject, animated: !(loadObject.requestParameters?.shouldNotAnimatePush ?? false)) + MVMCoreLoadRequestOperation.loadFinished(loadObject, loadedViewController: nil, errorObject: nil) + } catch { + MVMCoreLoadRequestOperation.loadFinished(loadObject, loadedViewController: nil, errorObject: MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreLoadHandler.sharedGlobal()?.errorLocation(forRequest: loadObject))) + } + } + } + + @objc + func display(viewController: UIViewController, loadObject: MVMCoreLoadObject?, completionHandler: @escaping () -> Void) { + Task(priority: .high) { + guard let navigationOperation = await NavigationHandler.shared().getNavigationOperation(with: viewController, loadObject: loadObject) else { + completionHandler() + return + } + // stop any loading animation we may have started if we are about to display + cancellable = NavigationHandler.shared().onNavigationWillBegin.sink(receiveValue: { operation in + if navigationOperation == operation, + !self.backgroundLoad, + !(loadObject?.requestParameters?.noloadingOverlay ?? false) { + MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(false) + } + }) + await NavigationHandler.shared().navigate(with: navigationOperation) + cancellable = nil + completionHandler() + } + } +} + +public extension NavigationHandler { + func popToViewController(with pageType: String, navigationController: UINavigationController? = nil, delegateObject: DelegateObject? = nil, animated: Bool = true) async throws { + guard let navigationController = navigationController ?? NavigationHandler.shared().navigationController else { + throw PopBackError.noNavigationHandler + } + guard let viewController = await navigationController.viewControllers.first(where: { viewController in + (viewController as? MVMCoreViewManagerProtocol)?.containsPage(withPageType: pageType) == true || + (viewController as? MVMCoreViewControllerProtocol)?.pageType == pageType + }) else { + throw PopBackError.noViewController + } + await NavigationHandler.shared().navigate(with: .popTo(viewController: viewController, navigationController: navigationController, animated: animated), delegateObject: delegateObject) + } +} diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h index e034161..1f2bce5 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h @@ -31,6 +31,8 @@ /// Legacy flag for if this operation will have an alert to show when finished. @property (nonatomic, readonly) BOOL alertToShow; +@property (nullable, strong, nonatomic) id cancellable; + // Initializes the operation with the request parameters object, data for page, and mvm view controller to handle the loading with. - (nullable instancetype)initWithRequestParameters:(nullable MVMCoreRequestParameters *)requestParameters dataForPage:(nullable NSDictionary *)dataForPage delegateObject:(nullable DelegateObject *)delegateObject backgroundLoad:(BOOL)backgroundLoad; diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m index d31592e..5b5cb12 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m @@ -482,9 +482,7 @@ } else if ([ValuePresentationStylePopToPage isEqualToString:[loadObject.pageJSON string:KeyPresentationStyle]] && !loadObject.operation.backgroundLoad && !loadObject.requestParameters.noViewControllerToLoad) { // We need to pop back to a page instead of loading a new page. - [[MVMCoreNavigationHandler sharedNavigationHandler] popToViewControllerWithPageType:[loadObject.pageJSON string:@"popBackPageType"] navigationController:loadObject.requestParameters.navigationController animated:!loadObject.requestParameters.shouldNotAnimatePush delegate:loadObject.delegateObject.presentationDelegate completionHandler:^{ - [MVMCoreLoadRequestOperation loadFinished:loadObject loadedViewController:nil errorObject:error]; - }]; + [loadObject.operation popBackToPageFor:loadObject]; // Notify listeners of module/pages loaded. [MVMCoreLoadRequestOperation notifyListenersOfNewResponse:pages modules:modules systemParameters:systemParameters loadObject:loadObject]; @@ -776,7 +774,7 @@ } // Once displyed, we are finished. - [MVMCoreActionUtility displayViewController:viewController forLoadObject:loadObject presentationDelegate:loadObject.delegateObject.presentationDelegate completionHandler:^{ + [loadObject.operation displayWithViewController:viewController loadObject:loadObject completionHandler:^{ [MVMCoreLoadRequestOperation loadFinished:loadObject loadedViewController:viewController errorObject:error]; }]; } diff --git a/MVMCore/MVMCore/PresentationHandling/NavigationHandler.swift b/MVMCore/MVMCore/PresentationHandling/NavigationHandler.swift index 8bac0df..7a18051 100644 --- a/MVMCore/MVMCore/PresentationHandling/NavigationHandler.swift +++ b/MVMCore/MVMCore/PresentationHandling/NavigationHandler.swift @@ -68,12 +68,56 @@ public class NavigationHandler { /** Publishes when the navigation will begin - Parameter NavigationType: The type of navigation being performed. */ - public let onNavigationWillBegin = PassthroughSubject<(NavigationType), Never>() + public let onNavigationWillBegin = PassthroughSubject<(NavigationOperation), Never>() /** Publishes when the navigation did finish - Parameter NavigationType: The type of navigation being performed. */ - public let onNavigationDidFinish = PassthroughSubject<(NavigationType), Never>() + public let onNavigationDidFinish = PassthroughSubject<(NavigationOperation), Never>() + + // MARK: - Getters + + /** + The base view controller for presentation when none are passed in. + Either the convenience ``viewControllerToPresentOn`` or the rootViewController of the window ``MVMCoreGetterUtility/getKeyWindow()`` + - Returns: The base view controller for presentation. + */ + @MainActor + public func getViewControllerToPresentOn() -> UIViewController? { + return viewControllerToPresentOn ?? MVMCoreGetterUtility.getKeyWindow()?.rootViewController + } + + /** + Get the top most presented view controller to present on. + Loops through presentedViewController on ``getViewControllerToPresentOn()`` + - Returns: The top most view controller to present on. + */ + @MainActor + public func getTopMostPresentedViewController() -> UIViewController? { + var viewController = getViewControllerToPresentOn()?.presentedViewController + while let presentedController = viewController?.presentedViewController { + viewController = presentedController + } + return viewController + } + + /** + Get the viewControllers of the navigation controller. + Takes into account the current animation state ``NavigationOperation/toNavigationControllerViewControllers`` + - Parameter navigationController: The navigation controller to fetch the viewcontrollers for. + - Returns: The viewControllers. + */ + @MainActor + public func getViewControllers(for navigationController: UINavigationController) -> [UIViewController] { + // Check if we are currently animating. + guard let operation = NavigationHandler.shared().navigationQueue.operations.first(where: { operation in + operation.isExecuting && (operation as? NavigationOperation) != nil + }) as? NavigationOperation, + navigationController == operation.navigationType.getNavigationController(), + let futureViewControllers = operation.toNavigationControllerViewControllers + else { return navigationController.viewControllers } + return futureViewControllers + } // MARK: - Navigation Functions @@ -102,30 +146,6 @@ public class NavigationHandler { await navigate(with: operation) } - /** - The base view controller for presentation when none are passed in. - Either the convenience ``viewControllerToPresentOn`` or the rootViewController of the window ``MVMCoreGetterUtility/getKeyWindow()`` - - Returns: The base view controller for presentation. - */ - @MainActor - public func getViewControllerToPresentOn() -> UIViewController? { - return viewControllerToPresentOn ?? MVMCoreGetterUtility.getKeyWindow()?.rootViewController - } - - /** - Get the top most presented view controller to present on. - Loops through presentedViewController on ``getViewControllerToPresentOn()`` - - Returns: The top most view controller to present on. - */ - @MainActor - public func getTopMostPresentedViewController() -> UIViewController? { - var viewController = getViewControllerToPresentOn()?.presentedViewController - while let presentedController = viewController?.presentedViewController { - viewController = presentedController - } - return viewController - } - public func push(viewController: UIViewController, navigationController: UINavigationController? = nil, delegateObject: DelegateObject? = nil, animated: Bool = true) async { guard let navigationController = navigationController ?? self.navigationController else { return } await NavigationHandler.shared().navigate(with: .push(viewController: viewController, navigationController: navigationController, animated: animated), delegateObject: delegateObject) @@ -182,6 +202,15 @@ extension UINavigationController { } } +@objc +public extension UINavigationController { + /// See ``NavigationHandler/getViewControllers(for:)`` + @objc @MainActor + func getViewControllers() -> [UIViewController] { + NavigationHandler.shared().getViewControllers(for: self) + } +} + extension UIViewController { /** Get the top most presented view controller to present on. @@ -196,111 +225,3 @@ extension UIViewController { return controllerToPresentOn } } - -/// Legacy objc ``NavigationHandler`` wrapper. -@objc public class MVMCoreNavigationHandler: NSObject { - - @objc(sharedNavigationHandler) - public static func shared() -> MVMCoreNavigationHandler? { - return MVMCoreNavigationHandler() - } - - @objc - public weak var viewControllerToPresentOn: UIViewController? { - get { - return NavigationHandler.shared().viewControllerToPresentOn - } - set { - NavigationHandler.shared().viewControllerToPresentOn = newValue - } - } - - @objc - public func popTopViewControllerAnimated(_ animated: Bool) { - Task(priority: .high) { @MainActor in - await NavigationHandler.shared().popTopViewController() - } - } - - @objc(presentViewController:animated:) - public func present(_ viewController: UIViewController, animated: Bool) { - Task(priority: .high) { @MainActor in - await NavigationHandler.shared().present(viewController: viewController, animated: animated) - } - } - - @objc(dismissTopViewControllerAnimated:) - public func dismissTopViewController(animated: Bool) { - Task(priority: .high) { @MainActor in - await NavigationHandler.shared().dismissTopViewController(animated: animated) - } - } - - @objc - public func popToViewControllerWithPageType(_ pageType: String, navigationController: UINavigationController?, animated: Bool = true, delegate: MVMCorePresentationDelegateProtocol?, completionHandler:(() -> Void)?) { - Task(priority: .high) { @MainActor in - guard let navigationController = navigationController ?? NavigationHandler.shared().navigationController, - let viewController = navigationController.viewControllers.first(where: { viewController in - (viewController as? MVMCoreViewControllerProtocol)?.pageType == pageType - }) else { - completionHandler?() - return - } - await NavigationHandler.shared().navigate(with: .popTo(viewController: viewController, navigationController: navigationController, animated: animated)) - completionHandler?() - } - } - - @objc - public func navigateWithLoadObject(_ loadObject: MVMCoreLoadObject?, viewController: UIViewController, delegate: MVMCorePresentationDelegateProtocol?, completionHandler:(() -> Void)?) { - Task(priority: .high) { - let delegateObject = DelegateObject() - delegateObject.presentationDelegate = delegate - guard let requestParameters = loadObject?.requestParameters else { - // Push - await NavigationHandler.shared().push(viewController:viewController, delegateObject:delegateObject, animated:true) - completionHandler?() - return - } - let shouldAnimate = !requestParameters.shouldNotAnimatePush - let navigationController = requestParameters.navigationController ?? NavigationHandler.shared().navigationController - switch requestParameters.loadStyle { - case .replaceCurrent: - await NavigationHandler.shared().replace(viewController: viewController, navigationController: navigationController, delegateObject: delegateObject, animated: shouldAnimate) - completionHandler?() - case .onTopOfRoot: - guard let root = await navigationController?.viewControllers.first else { return } - let viewControllers = [root, viewController] - await NavigationHandler.shared().set(viewControllers: viewControllers, navigationController: navigationController, delegateObject: delegateObject, animated: shouldAnimate) - completionHandler?() - case .becomeRoot: - await NavigationHandler.shared().set(viewControllers: [viewController], navigationController: navigationController, delegateObject: delegateObject, animated: shouldAnimate) - completionHandler?() - case .present: - await NavigationHandler.shared().present(viewController: viewController, animated: shouldAnimate) - completionHandler?() - default: - await NavigationHandler.shared().push(viewController: viewController, navigationController: navigationController, delegateObject: delegateObject, animated: shouldAnimate) - completionHandler?() - } - } - } - - /** - Get the viewControllers of the navigation controller. - Takes into account the current animation state ``NavigationOperation/toNavigationControllerViewControllers`` - - Parameter navigationController: The navigation controller to fetch the viewcontrollers for. - - Returns: The viewControllers. - */ - @objc(getViewControllersForNavigationController:) - public func getViewControllers(for navigationController: UINavigationController) -> [UIViewController] { - // Check if we are currently animating. - guard let operation = NavigationHandler.shared().navigationQueue.operations.first(where: { operation in - operation.isExecuting && (operation as? NavigationOperation) != nil - }) as? NavigationOperation, - navigationController == operation.navigationType.getNavigationController(), - let futureViewControllers = operation.toNavigationControllerViewControllers - else { return navigationController.viewControllers } - return futureViewControllers - } -} diff --git a/MVMCore/MVMCore/PresentationHandling/NavigationOperation.swift b/MVMCore/MVMCore/PresentationHandling/NavigationOperation.swift index 0d15c92..26ef9e5 100644 --- a/MVMCore/MVMCore/PresentationHandling/NavigationOperation.swift +++ b/MVMCore/MVMCore/PresentationHandling/NavigationOperation.swift @@ -28,7 +28,7 @@ open class NavigationOperation: MVMCoreOperation, UINavigationControllerDelegate } open override func main() { - checkAndHandleForCancellation() + guard !checkAndHandleForCancellation() else { return } Task(priority: .high) { @MainActor in try Task.checkCancellation() @@ -116,7 +116,8 @@ open class NavigationOperation: MVMCoreOperation, UINavigationControllerDelegate } navigationController.delegate = self toNavigationControllerViewControllers = viewControllers - NavigationHandler.shared().onNavigationWillBegin.send(navigationType) + NavigationHandler.shared().onNavigationWillBegin.send(self) + guard !checkAndHandleForCancellation() else { return } navigationController.setViewControllers(viewControllers, animated: animated) } @@ -174,9 +175,10 @@ open class NavigationOperation: MVMCoreOperation, UINavigationControllerDelegate */ @MainActor open func present(viewController: UIViewController, onController: UIViewController, animated: Bool = true) { - NavigationHandler.shared().onNavigationWillBegin.send(navigationType) + NavigationHandler.shared().onNavigationWillBegin.send(self) + guard !checkAndHandleForCancellation() else { return } onController.getViewControllerToPresentOn().present(viewController, animated: animated) { [self] in - NavigationHandler.shared().onNavigationDidFinish.send(navigationType) + NavigationHandler.shared().onNavigationDidFinish.send(self) markAsFinished() } } @@ -187,9 +189,10 @@ open class NavigationOperation: MVMCoreOperation, UINavigationControllerDelegate */ @MainActor open func dismiss(viewController: UIViewController, animated: Bool = true) { - NavigationHandler.shared().onNavigationWillBegin.send(navigationType) + NavigationHandler.shared().onNavigationWillBegin.send(self) + guard !checkAndHandleForCancellation() else { return } viewController.dismiss(animated: animated) { [self] in - NavigationHandler.shared().onNavigationDidFinish.send(navigationType) + NavigationHandler.shared().onNavigationDidFinish.send(self) markAsFinished() } } @@ -198,7 +201,7 @@ open class NavigationOperation: MVMCoreOperation, UINavigationControllerDelegate open func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { delegate?.navigationController?(navigationController, didShow: viewController, animated: animated) - NavigationHandler.shared().onNavigationDidFinish.send(navigationType) + NavigationHandler.shared().onNavigationDidFinish.send(self) markAsFinished() } diff --git a/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility+Extension.swift b/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility+Extension.swift index f6b45ce..ab849ed 100644 --- a/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility+Extension.swift +++ b/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility+Extension.swift @@ -17,3 +17,79 @@ public extension MVMCoreActionUtility { return instance } } + +@objc public extension MVMCoreActionUtility { + /// Can call to display a view controller based on the load object in the same way the architecture does it. Will check the presentation style of the page, action, and request. Will check if needs a view manager. + @objc(displayViewController:forLoadObject:completionHandler:) + static func display(_ viewController: UIViewController & MVMCoreViewControllerProtocol, for loadObject: MVMCoreLoadObject?, completionHandler: (() -> Void)?) { + Task(priority: .high) { + guard let navigationOperation = await NavigationHandler.shared().getNavigationOperation(with: viewController, loadObject: loadObject) else { + completionHandler?() + return + } + await NavigationHandler.shared().navigate(with: navigationOperation) + completionHandler?() + } + } +} + +public extension NavigationHandler { + + /// Gets the navigation operation based on the requestParameters. + @MainActor + func getNavigationOperation(with requestParameters: MVMCoreRequestParameters, viewController: UIViewController, delegateObject: DelegateObject?) -> NavigationOperation? { + + // Lets the server determine the presentation style if not explicitely set by developer. + if requestParameters.loadStyle == .default, + let presentationStyle = requestParameters.actionMap?.optionalStringForKey(KeyPresentationStyle) { + // Sets it based on the action map. + requestParameters.setMFLoadStyleBasedOnPresentationStyle(presentationStyle) + } + + let shouldAnimate = !requestParameters.shouldNotAnimatePush + switch requestParameters.loadStyle { + case .replaceCurrent: + guard let navigationController = requestParameters.navigationController ?? NavigationHandler.shared().navigationController else { return nil } + return NavigationOperation(with: .replace(viewController: viewController, navigationController: navigationController, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate) + case .onTopOfRoot: + guard let navigationController = requestParameters.navigationController ?? NavigationHandler.shared().navigationController, + let root = navigationController.viewControllers.first else { return nil } + let viewControllers = [root, viewController] + return NavigationOperation(with: .set(viewControllers: viewControllers, navigationController: navigationController, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate) + case .becomeRoot: + guard let navigationController = requestParameters.navigationController ?? NavigationHandler.shared().navigationController else { return nil } + return NavigationOperation(with: .set(viewControllers: [viewController], navigationController: navigationController, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate) + case .present: + guard let viewControllerToPresentOn = getViewControllerToPresentOn() else { return nil } + return NavigationOperation(with: .present(viewController: viewController, onController: viewControllerToPresentOn, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate) + default: + guard let navigationController = requestParameters.navigationController ?? NavigationHandler.shared().navigationController else { return nil } + return NavigationOperation(with: .push(viewController: viewController, navigationController: navigationController, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate) + } + } + + /// Gets the navigation operation based on the loadObject. + @MainActor + func getNavigationOperation(with viewController: UIViewController, loadObject: MVMCoreLoadObject?) -> NavigationOperation? { + // Lets the server determine the presentation style if not explicitely set by developer. + if loadObject?.requestParameters?.loadStyle == .default, + let presentationStyle = loadObject?.pageJSON?.optionalStringForKey(KeyPresentationStyle) { + // Let's the response override if needed. + loadObject?.requestParameters?.setMFLoadStyleBasedOnPresentationStyle(presentationStyle) + } + + // Check if we need to load on another control + var viewController = viewController + if let coreViewController = viewController as? UIViewController & MVMCoreViewControllerProtocol, + let viewControllerToLoad = MVMCoreObject.sharedInstance()?.globalLoadDelegate?.getViewManager?(with: coreViewController, loadObject: loadObject) { + viewController = viewControllerToLoad + } + + guard let requestParameters = loadObject?.requestParameters else { + // Push + guard let navigationController = NavigationHandler.shared().navigationController else { return nil } + return NavigationOperation(with: .push(viewController: viewController, navigationController: navigationController)) + } + return NavigationHandler.shared().getNavigationOperation(with: requestParameters, viewController: viewController, delegateObject: loadObject?.delegateObject) + } +} diff --git a/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility.h b/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility.h index b3abbd8..cb968a1 100644 --- a/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility.h +++ b/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility.h @@ -10,7 +10,6 @@ #import #import #import -#import @interface MVMCoreActionUtility : NSObject @@ -20,9 +19,6 @@ // Links away to safari or another app. Returns if successful. + (BOOL)linkAway:(nullable NSString *)safariURLString appURLString:(nullable NSString *)appURLString; -// Can call to display a view controller based on the load object in the same way the architecture does it. Will check the presentation style of the page, action, and request. Will check if needs a view manager. -+ (void)displayViewController:(nonnull UIViewController *)viewController forLoadObject:(nullable MVMCoreLoadObject *)loadObject presentationDelegate:(nullable NSObject*)delegate completionHandler:(nullable void (^)(void))completionBlock; - // returns if the class is not the same or a subclass of the other class. Can pass throw an exception as well + (BOOL)classIsInstanceTypeOfClass:(nonnull Class)theClass otherClass:(nonnull Class)otherClass throwException:(BOOL)throwException; diff --git a/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility.m b/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility.m index 354e97e..3cb82df 100644 --- a/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility.m +++ b/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility.m @@ -104,32 +104,6 @@ [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:completion]; } -+ (void)displayViewController:(nonnull UIViewController *)viewController forLoadObject:(nullable MVMCoreLoadObject *)loadObject presentationDelegate:(nullable NSObject*)delegate completionHandler:(nullable void (^)(void))completionBlock { - - // Lets the server determine the presentation style if not explicitely set by developer. - if (loadObject.requestParameters.loadStyle == MFLoadStyleDefault) { - - // Sets it first based on the action map. - NSString *presentationStyle = [loadObject.requestParameters.actionMap stringForKey:KeyPresentationStyle]; - [loadObject.requestParameters setMFLoadStyleBasedOnPresentationStyle:presentationStyle]; - - // Let's the response override if needed. - presentationStyle = [loadObject.pageJSON stringForKey:KeyPresentationStyle]; - [loadObject.requestParameters setMFLoadStyleBasedOnPresentationStyle:presentationStyle]; - } - - // Check if we need to load on another control - __block UIViewController *viewControllerToLoad = nil; - if ([[MVMCoreObject sharedInstance].globalLoadDelegate respondsToSelector:@selector(getViewManagerWithViewController:loadObject:)]) { - [MVMCoreDispatchUtility performSyncBlockOnMainThread:^{ - viewControllerToLoad = [[MVMCoreObject sharedInstance].globalLoadDelegate getViewManagerWithViewController:viewController loadObject:loadObject]; - }]; - } - - // Navigates - [[MVMCoreNavigationHandler sharedNavigationHandler] navigateWithLoadObject:loadObject viewController:(viewControllerToLoad ?: viewController) delegate:delegate completionHandler:completionBlock]; -} - + (BOOL)classIsInstanceTypeOfClass:(Class)theClass otherClass:(Class)otherClass throwException:(BOOL)throwException { if (theClass != otherClass && ![theClass isSubclassOfClass:otherClass]) { if (throwException) {