From ecf036ec782aa7e6f06f673075dfb5bd21862e84 Mon Sep 17 00:00:00 2001 From: Javier Cicchelli Date: Sun, 16 Apr 2023 14:42:19 +0200 Subject: [PATCH] Implemented the PushNavigationRouter and the ModalNavigationRouter routers for iOS platforms, with its respective BaseNavigationRouter base class. --- .../iOS/Routers/BaseNavigationRouter.swift | 68 +++++++++++++++ .../iOS/Routers/ModalNavigationRouter.swift | 85 +++++++++++++++++++ .../iOS/Routers/PushNavigationRouter.swift | 64 ++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 Sources/Coordinator/Platform/iOS/Routers/BaseNavigationRouter.swift create mode 100644 Sources/Coordinator/Platform/iOS/Routers/ModalNavigationRouter.swift create mode 100644 Sources/Coordinator/Platform/iOS/Routers/PushNavigationRouter.swift diff --git a/Sources/Coordinator/Platform/iOS/Routers/BaseNavigationRouter.swift b/Sources/Coordinator/Platform/iOS/Routers/BaseNavigationRouter.swift new file mode 100644 index 0000000..5d6ad45 --- /dev/null +++ b/Sources/Coordinator/Platform/iOS/Routers/BaseNavigationRouter.swift @@ -0,0 +1,68 @@ +// +// BaseNavigationRouter.swift +// Coordinator +// +// Created by Javier Cicchelli on 11/04/2023. +// Copyright © 2023 Röck+Cöde. All rights reserved. +// + +import UIKit + +/// This is a base class for the `NavigationRouter` concrete router implementations. +public class BaseNavigationRouter: NSObject { + + // MARK: Properties + + /// A navigation controller to use within this concrete router. + var navigationController: UINavigationController + + /// Dictionary that persist `onDismiss` closure for its respective view controllers until one of the later is dismissed. + var onDismissForViewController: [UIViewController: Router.OnDismissedClosure] = [:] + + // MARK: Initialisers + + /// Initialise this router. + /// - Parameter navigationController: A `UINavigationController` navigation controller instance to use in this router. + init(navigationController: UINavigationController) { + self.navigationController = navigationController + + super.init() + + self.navigationController.delegate = self + } + + // MARK: Functions + + /// Executes the `onDismiss` closure for a given view controller. + /// - Parameter viewController: A `UIViewController` view controller instance for which the on dismiss closure will be executed. + func performOnDismissed(for viewController: UIViewController) { + guard let onDismiss = onDismissForViewController[viewController] else { + return + } + + onDismiss() + + onDismissForViewController[viewController] = nil + } + +} + +// MARK: - UINavigationControllerDelegate + +extension BaseNavigationRouter: UINavigationControllerDelegate { + + // MARK: Functions + + public func navigationController( + _ navigationController: UINavigationController, + didShow viewController: UIViewController, + animated: Bool + ) { + guard let dismissedViewController = navigationController.transitionCoordinator?.viewController(forKey: .from) else { + return + } + + performOnDismissed(for: dismissedViewController) + } + +} diff --git a/Sources/Coordinator/Platform/iOS/Routers/ModalNavigationRouter.swift b/Sources/Coordinator/Platform/iOS/Routers/ModalNavigationRouter.swift new file mode 100644 index 0000000..ed7e070 --- /dev/null +++ b/Sources/Coordinator/Platform/iOS/Routers/ModalNavigationRouter.swift @@ -0,0 +1,85 @@ +// +// ModalNavigationRouter.swift +// Coordinator +// +// Created by Javier Cicchelli on 11/04/2023. +// Copyright © 2023 Röck+Cöde. All rights reserved. +// + +import UIKit + +public class ModalNavigationRouter: BaseNavigationRouter { + + // MARK: Properties + + /// The parent view controller from where this router is being called from. + public unowned let parentViewController: UIViewController + + // MARK: Initialisers + + /// Initialise this router. + /// - Parameter parentViewController: A `UIViewController` view controller instance from where this router is originated. + public init(parentViewController: UIViewController) { + self.parentViewController = parentViewController + + super.init(navigationController: .init()) + } + +} + +extension ModalNavigationRouter: Router { + public func present( + _ viewController: UIViewController, + animated: Bool, + onDismiss: OnDismissedClosure? + ) { + onDismissForViewController[viewController] = onDismiss + + if navigationController.viewControllers.isEmpty { + presentModally(viewController, animated: animated) + } else { + navigationController.pushViewController(viewController, animated: animated) + } + } + + public func dismiss(animated: Bool) { + guard let firstViewController = navigationController.viewControllers.first else { + return + } + + performOnDismissed(for: firstViewController) + + parentViewController.dismiss(animated: animated) + } + +} + +// MARK: - Helpers + +private extension ModalNavigationRouter { + + // MARK: Functions + + func presentModally(_ viewController: UIViewController, animated: Bool) { + viewController.navigationItem.leftBarButtonItem = UIBarButtonItem( + title: "Cancel", + style: .plain, + target: self, + action: #selector(onCancelPressed) + ) + + navigationController.setViewControllers([viewController], animated: false) + + parentViewController.present(navigationController, animated: animated) + } + + @objc func onCancelPressed() { + guard let firstViewController = navigationController.viewControllers.first else { + return + } + + performOnDismissed(for: firstViewController) + dismiss(animated: true) + } + +} diff --git a/Sources/Coordinator/Platform/iOS/Routers/PushNavigationRouter.swift b/Sources/Coordinator/Platform/iOS/Routers/PushNavigationRouter.swift new file mode 100644 index 0000000..c152309 --- /dev/null +++ b/Sources/Coordinator/Platform/iOS/Routers/PushNavigationRouter.swift @@ -0,0 +1,64 @@ +// +// PushNavigationRouter.swift +// Coordinator +// +// Created by Javier Cicchelli on 11/04/2023. +// Copyright © 2023 Röck+Cöde. All rights reserved. +// + +import UIKit + +/// This class is responsible for presenting view controllers, as it is a concrete implementation of the `Router` protocol, but it won't know what view controller or which view controller is next. +public class PushNavigationRouter: BaseNavigationRouter { + + // MARK: Properties + + /// A root view controller coming in from the navigation controller, if any. + private let rootViewController: UIViewController? + + // MARK: Initialisers + + /// Initialise this router. + /// - Parameters: + /// - navigationController: A `UINavigationController` navigation controller instance to use in this router. + /// - rootViewController: A `UIViewController` view controller instance to define as a root view controller of the navigation controller. + /// - Note This initialiser added the `rootViewController` parameter although it is not really needed to differentiate itself from the `.init(navigationController:)` implemented for the `BaseNavigationRouter` base class. + public init( + navigationController: UINavigationController, + rootViewController: UIViewController? = nil + ) { + self.rootViewController = navigationController.viewControllers.first ?? rootViewController + + super.init(navigationController: navigationController) + } + +} + +// MARK: - Router + +extension PushNavigationRouter: Router { + + // MARK: Functions + + public func present( + _ viewController: UIViewController, + animated: Bool, + onDismiss: OnDismissedClosure? + ) { + onDismissForViewController[viewController] = onDismiss + + navigationController.pushViewController(viewController, animated: animated) + } + + public func dismiss(animated: Bool) { + guard let rootViewController else { + navigationController.popViewController(animated: animated) + return + } + + performOnDismissed(for: rootViewController) + + navigationController.popToViewController(rootViewController, animated: animated) + } + +}