// // NavigationController.swift // MVMCoreUI // // Created by Scott Pfeil on 10/24/19. // Copyright © 2019 Verizon Wireless. All rights reserved. // import UIKit @objcMembers open class NavigationController: UINavigationController, MVMCoreViewManagerViewControllerProtocol { public weak var manager: (UIViewController & MVMCoreViewManagerProtocol)? /// Getter for the main navigation controller public static func navigationController() -> Self? { return MVMCoreActionUtility.initializerClassCheck(MVMCoreUISession.sharedGlobal()?.navigationController, classToVerify: self) as? Self } /// Sets up the application with a navigation controller 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) NavigationController.setNavigationBarUI(navigationController: navigationController, navigationItemModel: NavigationItemModel()) return navigationController } /// Sets up the application with a navigation controller as the main container. public static func setupNavigationControllerAsMainController() -> Self? { guard let navigationController = setupNavigationController() else { return nil } MVMCoreUISession.sharedGlobal()?.setup(asStandardLoadViewDelegate: navigationController) return navigationController } /// Convenience function for setting the navigation item. public static func setNavigationItem(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol, viewController: UIViewController) { viewController.navigationItem.title = navigationItemModel.title viewController.navigationItem.accessibilityLabel = navigationItemModel.title viewController.navigationItem.hidesBackButton = navigationItemModel.hidesSystemBackButton viewController.navigationItem.leftItemsSupplementBackButton = !navigationItemModel.hidesSystemBackButton setNavigationButtons(navigationController: navigationController, navigationItemModel: navigationItemModel, viewController: viewController) setNavigationTitleView(navigationController: navigationController, navigationItemModel: navigationItemModel, viewController: viewController) } /// Convenience function for setting the navigation buttons. public static func setNavigationButtons(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol, viewController: UIViewController) { let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject var leftItems: [UIBarButtonItem] = [] if navigationItemModel.hidesSystemBackButton, navigationItemModel.alwaysShowBackButton != false { if let backButtonModel = navigationItemModel.backButton, MVMCoreNavigationHandler.shared()?.getViewControllers(for: navigationController)?.count ?? 0 > 1 || navigationItemModel.alwaysShowBackButton ?? false { leftItems.append(backButtonModel.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) } if let leftItemModels = navigationItemModel.additionalLeftButtons { for item in leftItemModels { leftItems.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) } } } viewController.navigationItem.leftBarButtonItems = leftItems.count > 0 ? leftItems : nil var rightItems: [UIBarButtonItem] = [] if let rightItemModels = navigationItemModel.additionalRightButtons { for item in rightItemModels { rightItems.append(item.createNavigationItemButton(delegateObject: delegate, additionalData: nil)) } } viewController.navigationItem.rightBarButtonItems = rightItems.count > 0 ? rightItems : nil } static func getNavigationBarShadowImage(for navigationItemModel: NavigationItemModelProtocol) -> UIImage? { guard let thickness = navigationItemModel.line?.thickness, let backgroundColor = navigationItemModel.line?.backgroundColor else { return nil } return backgroundColor.uiColor.image(CGSize(width: thickness, height: thickness)) } /// Convenience function for setting the navigation bar ui, except for the buttons. public static func setNavigationBarUI(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol) { let navigationBar = navigationController.navigationBar let font = MFStyler.fontBoldBodySmall(false) let backgroundColor = navigationItemModel.backgroundColor?.uiColor let tint = navigationItemModel.tintColor.uiColor navigationBar.tintColor = tint let appearance = UINavigationBarAppearance() appearance.configureWithOpaqueBackground() appearance.titleTextAttributes = [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: tint]; appearance.backgroundColor = backgroundColor appearance.titleTextAttributes.updateValue(tint, forKey: .foregroundColor) appearance.titlePositionAdjustment = navigationItemModel.titleOffset ?? .zero appearance.shadowColor = navigationItemModel.line?.backgroundColor?.uiColor ?? .clear appearance.shadowImage = getNavigationBarShadowImage(for: navigationItemModel)?.withRenderingMode(.alwaysTemplate) navigationBar.standardAppearance = appearance navigationBar.scrollEdgeAppearance = appearance navigationController.setNavigationBarHidden(navigationItemModel.hidden, animated: true) } /// Convenience function for setting the navigation titleView. public static func setNavigationTitleView(navigationController: UINavigationController, navigationItemModel: NavigationItemModelProtocol?, viewController: UIViewController) { let delegate = (viewController as? MVMCoreViewControllerProtocol)?.delegateObject?() as? MVMCoreUIDelegateObject if let titleViewModel = navigationItemModel?.titleView, let molecule = ModelRegistry.createMolecule(titleViewModel, delegateObject: delegate, additionalData: nil) { viewController.navigationItem.titleView = molecule } } /// Convenience function to return the navigation model of the lowest controller traversing managers if applicable. 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 { return false } return true } } extension NavigationController: MVMCoreViewManagerProtocol { public func getCurrentViewController() -> UIViewController? { guard let topViewController = topViewController else { return nil } return MVMCoreUIUtility.getViewControllerTraversingManagers(topViewController) } public func containsPage(withPageType pageType: String?) -> Bool { for controller in viewControllers { if let manager = controller as? MVMCoreViewManagerProtocol, manager.containsPage(withPageType: pageType) { return true } else if let controller = controller as? MVMCoreViewControllerProtocol, controller.pageType == pageType { return true } } return false } public func newDataReceived(in viewController: UIViewController) { if isDisplayed(viewController: viewController), let topViewController = topViewController, let model = getNavigationModel(from: viewController) { Self.setNavigationItem(navigationController: self, navigationItemModel: model, viewController: topViewController) Self.setNavigationBarUI(navigationController: self, navigationItemModel: model) } manager?.newDataReceived?(in: viewController) } public func willDisplay(_ viewController: UIViewController) { if let topViewController = topViewController, let model = getNavigationModel(from: viewController) { Self.setNavigationItem(navigationController: self, navigationItemModel: model, viewController: topViewController) } manager?.willDisplay?(viewController) } public func displayedViewController(_ viewController: UIViewController) { if isDisplayed(viewController: viewController), let model = getNavigationModel(from: viewController) { Self.setNavigationBarUI(navigationController: self, navigationItemModel: model) } manager?.displayedViewController?(viewController) } } extension NavigationController: MVMCorePresentationDelegateProtocol { public func navigationController(_ navigationController: UINavigationController, prepareDisplayFor viewController: UIViewController) { guard self == navigationController, let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController), let model = getNavigationModel(from: newViewController) else { return } Self.setNavigationItem(navigationController: self, navigationItemModel: model, viewController: viewController) } public func navigationController(_ navigationController: UINavigationController, willDisplay viewController: UIViewController) { guard self == navigationController, let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) else { return } if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) { MVMCoreViewManagerViewControllerProtocolHelper.helpSetManager(self, viewController: controller) } manager?.willDisplay?(newViewController) } public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { guard self == navigationController, let newViewController = MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) else { return } if let model = getNavigationModel(from: newViewController) { Self.setNavigationBarUI(navigationController: self, navigationItemModel: model) } 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 self.setFill() rendererContext.fill(CGRect(origin: .zero, size: size)) } } }