diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift index 139a3d06..b2cac773 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/Tabs.swift @@ -15,7 +15,6 @@ import UIKit @objcMembers open class Tabs: View, MVMCoreUIViewConstrainingProtocol, MFButtonProtocol { - public var tabsModel: TabsModel? { get { return model as? TabsModel } } @@ -77,7 +76,7 @@ import UIKit func setupCollectionView () { layout.scrollDirection = .horizontal - layout.minimumLineSpacing = cellSpacing + layout.minimumLineSpacing = 0 let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) collectionView.translatesAutoresizingMaskIntoConstraints = false @@ -182,6 +181,12 @@ extension Tabs: UICollectionViewDataSource { extension Tabs: UICollectionViewDelegateFlowLayout { public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + guard self.collectionView(collectionView, numberOfItemsInSection: indexPath.section) != 2 else { + // If two tabs, take up the screen + let insets = self.collectionView(collectionView, layout: collectionViewLayout, insetForSectionAt: indexPath.section) + let width = (collectionView.bounds.width / 2.0) - insets.left - insets.right + return CGSize(width: width, height: cellHeight) + } guard let labelModel = tabsModel?.tabs[indexPath.row].label else { return .zero } @@ -209,6 +214,10 @@ extension Tabs: UICollectionViewDelegateFlowLayout { } public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + // If two tabs, take up the screen, no space between items + guard self.collectionView(collectionView, numberOfItemsInSection: section) != 2 else { + return 0 + } return sectionPadding } @@ -237,7 +246,7 @@ extension Tabs: UICollectionViewDelegateFlowLayout { guard let collect = collectionView, tabsModel?.tabs.count ?? 0 > 0 else { return } collect.selectItem(at: indexPath, animated: animated, scrollPosition: .centeredHorizontally) - guard let tabCell = collect.cellForItem(at: indexPath) as? TabItemCell, let tabsModel = self.tabsModel else { return } + guard let tabCell = collect.cellForItem(at: indexPath) as? TabItemCell, let tabsModel = tabsModel else { return } moveSelectionLine(toIndex: indexPath, animated: animated, cell: tabCell) tabCell.label.textColor = tabsModel.selectedColor.uiColor tabCell.updateAccessibility(indexPath: indexPath, selected: true, tabsModel: tabsModel) @@ -270,10 +279,10 @@ extension Tabs: UIScrollViewDelegate { //------------------------------------------------- extension Tabs { func moveSelectionLine(toIndex indexPath: IndexPath, animated: Bool, cell: TabItemCell) { - guard let collect = self.collectionView else {return} + guard let collect = collectionView else {return} let size = collectionView(collect, layout: layout, sizeForItemAt: indexPath) - let barWidth = max(size.width, selectionLineWidth) + let barWidth = getLineWidth(for: indexPath) let animationBlock = { [weak self] in let x = cell.frame.origin.x @@ -287,6 +296,39 @@ extension Tabs { animationBlock() } } + + func getLineWidth(for indexPath: IndexPath) -> CGFloat { + guard let collection = collectionView else { return 0 } + let width = collectionView(collection, layout: layout, sizeForItemAt: indexPath).width + guard collectionView(collection, numberOfItemsInSection: indexPath.section) != 2 else { + return width + } + return max(width, selectionLineWidth) + } + + func progress(from index: Int, toIndex: Int, percentage: CGFloat) { + let fromIndexPath = IndexPath(row: index, section: 0) + let toIndexPath = IndexPath(row: toIndex, section: 0) + guard let collection = collectionView, + let fromCell = collection.cellForItem(at: fromIndexPath), + let toCell = collection.cellForItem(at: toIndexPath) else { return } + + let fromLineWidth = getLineWidth(for: fromIndexPath) + let toLineWidth = getLineWidth(for: toIndexPath) + let finalLineWidth = (toLineWidth - fromLineWidth) * percentage + fromLineWidth + selectionLineWidthConstraint?.constant = finalLineWidth + + // setting the x for percentage + let fromCellWidth = collectionView(collection, layout: layout, sizeForItemAt: fromIndexPath).width + let toCellWidth = collectionView(collection, layout: layout, sizeForItemAt: toIndexPath).width + let originalX = fromCell.frame.origin.x + ((fromCellWidth - fromLineWidth) / 2.0) + let toX = toCell.frame.origin.x + ((toCellWidth - toLineWidth) / 2.0) + let xDifference = toX - originalX + let finalX = (xDifference * percentage) + originalX + selectionLineLeftConstraint?.constant = finalX + + bottomContentView.layoutIfNeeded() + } } @objcMembers public class TabItemCell: CollectionViewCell { diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabsModel.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabsModel.swift index 58990428..398d7c59 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabsModel.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TabsModel.swift @@ -66,11 +66,23 @@ public class TabItemModel: Codable { case action } + func setDefaults() { + if label.textAlignment == nil { + label.textAlignment = .center + } + } + + public init(with label: LabelModel, action: ActionModelProtocol?) { + self.label = label + self.action = action + setDefaults() + } required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) label = try typeContainer.decode(LabelModel.self, forKey: .label) action = try typeContainer.decodeModelIfPresent(codingKey: .action) + setDefaults() } public func encode(to encoder: Encoder) throws { diff --git a/MVMCoreUI/Managers/SubNav/SubNavInteractor.swift.swift b/MVMCoreUI/Managers/SubNav/SubNavInteractor.swift.swift new file mode 100644 index 00000000..39a71bf3 --- /dev/null +++ b/MVMCoreUI/Managers/SubNav/SubNavInteractor.swift.swift @@ -0,0 +1,144 @@ +// +// SubNavInteractor.swift +// MobileFirstFramework +// +// Created by Matt Bruce on 10/22/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objc public protocol SubNavSwipeNavigationProtocol: AnyObject { + func swipeLeft() + func swipeRight() + func update(percentage: CGFloat) +} + +fileprivate enum SubNavPanningDirection : Int { + case left + case right + case none +} + +@objcMembers public class SubNavInteractor: UIPercentDrivenInteractiveTransition { + + //// true while panning + public var panning: Bool = false + + ////Pan gesture instance + public var panGesture: UIPanGestureRecognizer = .init() + + ////set pannable percentage 0 to 1 + public var pannablePercentage: CGFloat = 0.0 + + //// can be used to keep track of if we are .. + public var interactive: Bool = false + + //private + private var shouldCompleteTransition: Bool = false + private weak var delegate: SubNavSwipeNavigationProtocol? + private var panningDirection: SubNavPanningDirection = .none + + ////constructor + public convenience init(viewController: (UIViewController & SubNavSwipeNavigationProtocol)) { + self.init(viewController: viewController, delegate: viewController) + } + + public init(viewController: UIViewController, delegate: SubNavSwipeNavigationProtocol) { + self.pannablePercentage = 0.15 + self.delegate = delegate + super.init() + self.panGesture = UIPanGestureRecognizer.init(target: self, action: #selector(handlePanGesture)); + viewController.view.addGestureRecognizer(self.panGesture) + } + + //overrides + public override func cancel() { + if interactive { + delegate?.update(percentage: 0) + super.cancel() + } + interactive = false + panning = false + shouldCompleteTransition = false + } + + public override func finish() { + if interactive { + delegate?.update(percentage: 1) + super.finish() + } + interactive = false + panning = false + shouldCompleteTransition = false + } + + //private + @objc private func handlePanGesture(_ pan: UIPanGestureRecognizer) { + guard let panView = pan.view else { return } + + let velocityX = pan.velocity(in: panView).x + let translation = pan.translation(in: panView) + var percentage = translation.x / panView.bounds.width + let locationInView = pan.location(in: panView) + + // Simulates an edge gesture by only accepting pans at the edge of the screen. Needed because edge gesture doesn't work nicely with extended menus such as on ipad. + let frame = panView.frame + let pannableFrameLeft = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width * pannablePercentage, height: frame.size.height) + let pannableFrameRight = CGRect(x: frame.origin.x + frame.size.width * (1 - pannablePercentage), y: frame.origin.y, width: frame.size.width * pannablePercentage, height: frame.size.height) + + switch (pan.state) { + + case .began: + // Begin the transition to the next page. + self.panning = true + if velocityX < 0 && pannableFrameRight.contains(locationInView) { + self.delegate?.swipeLeft() + self.panningDirection = .left + } else if velocityX > 0 && pannableFrameLeft.contains(locationInView){ + self.delegate?.swipeRight() + self.panningDirection = .right + } + + case .changed: + guard self.interactive else { return } + + if self.panningDirection == .right && translation.x < 0 || + self.panningDirection == .left && translation.x > 0 { + percentage = 0 + } + + if percentage < 0 { + percentage = -percentage; + } + if percentage > 1.00 { + percentage = 1.00; + } + self.shouldCompleteTransition = percentage > 0.65; + self.update(percentage) + self.delegate?.update(percentage: percentage) + + case .cancelled: + self.cancel() + + case .ended: + if ((percentage < 0) != (velocityX < 0)) && abs(velocityX) > 50 { + // If we are moving back toward the previous view we want to cancel. (the speed threshold is for shaky hands) + self.cancel() + } else if abs(velocityX) > 300.0 || shouldCompleteTransition { + // Need to cancel the transition if we pass the threshold, but from a opposite direction(e.g. Start panning from left edge to right, then move back, pass the start x location, end with a high velocityX) + if ((panningDirection == .right) && translation.x < 0) || ((panningDirection == .left) && translation.x > 0) { + self.cancel() + } else { + // If we pass the speed or screen percentage thresholds, move on to the next screen. + self.finish() + } + } else { + self.cancel() + } + + default: + return + } + } +} diff --git a/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift b/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift new file mode 100644 index 00000000..9480dd40 --- /dev/null +++ b/MVMCoreUI/Managers/SubNav/SubNavManagerController.swift @@ -0,0 +1,331 @@ +// +// SubNavManagerController.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 10/22/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation +import MVMCore + +open class SubNavManagerController: ViewController, MVMCoreViewManagerProtocol, TabsDelegate, MVMCorePresentationDelegateProtocol, SubNavSwipeNavigationProtocol { + + /// The current managed view controller + private var viewController: UIViewController + + /// A list of cached controllers. + private var viewControllers: [UIViewController?] + + private var tabsModel: TabsModel + public lazy var tabs: Tabs = { + let tabs = Tabs(model: tabsModel, delegateObjectIVar, nil) + tabs.delegate = self + return tabs + }() + + public lazy var line: Line = { + return Line(model: LineModel(type: .standard), delegateObjectIVar, nil) + }() + + public lazy var subNavigationController: UINavigationController = { + let subNavigationController = SubNavManagerNavigationController(rootViewController: viewController) + subNavigationController.view.translatesAutoresizingMaskIntoConstraints = false + return subNavigationController + }() + + /// Interactor for swiping. + public var customInteractor: SubNavInteractor? + + /// The index to go to. + private var index: Int? + + /// Flag for if we need to call a track action. + private var needToTrackTabSelect = false + + public init(viewController: UIViewController, tabsModel: TabsModel, loadObject: MVMCoreLoadObject, shouldEnableSwipeGestures: Bool) { + self.viewController = viewController + self.tabsModel = tabsModel + self.viewControllers = [UIViewController?](repeating: nil, count: tabsModel.tabs.count) + self.viewControllers[tabsModel.selectedIndex] = viewController + super.init(nibName: nil, bundle: nil) + setup(with: loadObject, shouldEnableSwipeGestures: shouldEnableSwipeGestures) + } + + public init(viewControllers: [UIViewController], tabsModel: TabsModel, loadObject: MVMCoreLoadObject, shouldEnableSwipeGestures: Bool) { + self.tabsModel = tabsModel + self.viewControllers = viewControllers + self.viewController = viewControllers[tabsModel.selectedIndex] + super.init(nibName: nil, bundle: nil) + setup(with: loadObject, shouldEnableSwipeGestures: shouldEnableSwipeGestures) + } + + func setup(with loadObject: MVMCoreLoadObject, shouldEnableSwipeGestures: Bool) { + self.loadObject = loadObject + pageType = loadObject.pageType + if shouldEnableSwipeGestures { + customInteractor = SubNavInteractor(viewController: self) + } + if let controller = viewController as? (UIViewController & MVMCoreViewManagerViewControllerProtocol) { + MVMCoreViewManagerViewControllerProtocolHelper.helpSetManager(self, viewController: controller) + } + hideNavigationBarLine(for: viewController) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + open override func loadView() { + let view = View() + view.translatesAutoresizingMaskIntoConstraints = true + + view.addSubview(tabs) + view.addSubview(line) + addChild(subNavigationController) + view.addSubview(subNavigationController.view) + subNavigationController.didMove(toParent: self) + + NSLayoutConstraint.activate([ + tabs.leadingAnchor.constraint(equalTo: view.leadingAnchor), + view.trailingAnchor.constraint(equalTo: tabs.trailingAnchor), + line.leadingAnchor.constraint(equalTo: view.leadingAnchor), + view.trailingAnchor.constraint(equalTo: line.trailingAnchor), + subNavigationController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + view.trailingAnchor.constraint(equalTo: subNavigationController.view.trailingAnchor), + tabs.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + line.topAnchor.constraint(equalTo: tabs.bottomAnchor), + subNavigationController.view.topAnchor.constraint(equalTo: line.bottomAnchor), + view.bottomAnchor.constraint(equalTo: subNavigationController.view.bottomAnchor) + ]) + self.view = view + } + + open override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + // Notify the view controller it is showing. + if manager == nil { + (viewController as? MVMCoreViewManagerViewControllerProtocol)?.viewControllerReady?(inManager: self) + } + } + + open override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + // Notify showing view we will disappear. + (viewController as? MVMCoreViewManagerViewControllerProtocol)?.managerWillDisappear?(self) + } + + /// Hides the navigation bar for the page. + open func hideNavigationBarLine(for viewController: UIViewController) { + (viewController as? PageProtocol)?.pageModel?.navigationBar?.line?.type = .none + } + + open override func updateViews() { + super.updateViews() + if screenSizeChanged() { + tabs.updateView(view.bounds.size.width) + } + } + + // MARK: - MVMCoreLoadDelegateProtocol + + open func shouldContinue(toErrorPage loadObject: MVMCoreLoadObject, error: MVMCoreErrorObject?) -> Bool { + // Push error screens so they do not replace the tab page. + loadObject.requestParameters?.navigationController = self.navigationController + loadObject.requestParameters?.loadStyle = .push + return true + } + + // MARK: - Action Related + + /// Logs the action for the selected tab. + open func trackSelectTab() { + guard let action = tabs.tabsModel?.tabs[tabs.selectedIndex].action, + let json = MVMCoreUIActionHandler.shared()?.convertActionToJSON(action, delegateObject: delegateObjectIVar) else { return } + MVMCoreUIActionHandler.shared()?.logAction(json, additionalData: getAdditionalDataForNewTabLoad(indexPath: IndexPath(row: tabs.selectedIndex, section: 0)), delegateObject: delegateObjectIVar) + } + + /// Allow override of additioonal data for tab press action + open func getAdditionalDataForNewTabLoad(indexPath: IndexPath) -> [AnyHashable: Any]? { + return ["tabBarPressed": true, KeySourceModel: tabsModel] + } + + /// Allows modification of requestParameters object for openPage request + open func getRequestParametersForNewTabLoad(requestParameters: MVMCoreRequestParameters, actionInformation: [AnyHashable : Any]?, additionalData: [AnyHashable : Any]?) -> MVMCoreRequestParameters { + requestParameters.navigationController = subNavigationController + requestParameters.loadStyle = .replaceCurrent + requestParameters.tabWasPressed = true + return requestParameters + } + + open override func handleOpenPage(for requestParameters: MVMCoreRequestParameters, actionInformation: [AnyHashable : Any]?, additionalData: [AnyHashable : Any]?) { + var requestParameters = requestParameters + guard let additionalData = additionalData, + additionalData.boolForKey("tabBarPressed") else { + // Pass to delegate + if (viewController as? MVMCoreActionDelegateProtocol)?.handleOpenPage?(for: requestParameters, actionInformation: actionInformation, additionalData: additionalData) == nil { + super.handleOpenPage(for: requestParameters, actionInformation: actionInformation, additionalData: additionalData) + } + return + } + // Allow opportunity to modify parameters. + requestParameters = getRequestParametersForNewTabLoad(requestParameters: requestParameters, actionInformation: actionInformation, additionalData: additionalData) + super.handleOpenPage(for: requestParameters, actionInformation: actionInformation, additionalData: additionalData) + } + + // MARK: - MVMCorePresentationDelegateProtocol + + public func navigationController(_ navigationController: UINavigationController, interactiveTransitionWasCanceled canceled: Bool) { + needToTrackTabSelect = false + index = nil + } + + public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { + guard navigationController == subNavigationController, + let index = index else { return nil } + + // Tab bars animate left and right navigation accordingly. + if tabs.selectedIndex < index { + return SubNavSwipeAnimator(swipingDirection: .left, controller: self) + } else if tabs.selectedIndex > index { + return SubNavSwipeAnimator(swipingDirection: .right, controller: self) + } else { + return nil + } + } + + public func navigationController(_ navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { + // Only percent interact if we've already loaded the view controller + guard let customInteractor = customInteractor, + let index = index, + customInteractor.panning, + viewControllers[index] != nil else { return nil } + customInteractor.interactive = true + return customInteractor + } + + /// Handles when the controller has committed to be changed. + private func commitTo(controller: UIViewController) { + guard let index = index, + index != tabs.selectedIndex else { return } + self.viewController = controller + pageType = (viewController as? MVMCoreViewControllerProtocol)?.pageType + hideNavigationBarLine(for: controller) + if let viewController = getCurrentViewController() { + manager?.willDisplay?(viewController) + } + tabs.selectIndex(index, animated: true) + self.index = nil + } + + public func navigationController(_ navigationController: UINavigationController, willDisplay viewController: UIViewController) { + guard navigationController == subNavigationController else { return } + + if let viewController = viewController as? UIViewController & MVMCoreViewManagerViewControllerProtocol & MVMCoreViewControllerProtocol { + MVMCoreViewManagerViewControllerProtocolHelper.helpSetManager(self, viewController: viewController) + + // Cache the controller. + if viewController.shouldCacheInManager?() ?? true, + let index = index { + viewControllers[index] = viewController + } + } + + // Wait to consider page loaded until after transition if we are custom animating swipe, otherwise do it now. + guard customInteractor?.interactive != true else { return } + commitTo(controller: viewController) + } + + public func navigationController(_ navigationController: UINavigationController, displayedViewController viewController: UIViewController) { + guard navigationController == subNavigationController else { return } + // Need to track swipe action. + if needToTrackTabSelect { + needToTrackTabSelect = false + trackSelectTab() + } + + // Consider the page loaded if we have not already + commitTo(controller: viewController) + (viewController as? MVMCoreViewManagerViewControllerProtocol)?.viewControllerReady?(inManager: self) + if let viewController = getCurrentViewController() { + manager?.displayedViewController?(viewController) + } + } + + // MARK: - TabsDelegate + + // We will manually manage tab selection due to possible swipe interactions. + open func shouldSelectItem(_ indexPath: IndexPath, tabs: Tabs) -> Bool { + guard tabs.selectedIndex != indexPath.row else { return false } + index = indexPath.row + 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) + } else if let tabsModel = tabs.tabsModel, + let action = tabsModel.tabs[indexPath.row].action { + // Perform the tab action + MVMCoreActionHandler.shared()?.asyncHandleAction(with: action, additionalData: getAdditionalDataForNewTabLoad(indexPath: indexPath), delegateObject: delegateObjectIVar) + } + return false + } + + // Not currently used. + open func didSelectItem(_ indexPath: IndexPath, tabs: Tabs) {} + + // MARK: - MVMCoreViewManagerViewControllerProtocol + + open override func viewControllerReady(inManager manager: UIViewController & MVMCoreViewManagerProtocol) { + // Pass on down + (viewController as? MVMCoreViewManagerViewControllerProtocol)?.viewControllerReady?(inManager: self) + } + + // MARK: - MVMCoreViewManagerProtocol + + open func getCurrentViewController() -> UIViewController? { + MVMCoreUIUtility.getViewControllerTraversingManagers(viewController) + } + + open func containsPage(withPageType pageType: String?) -> Bool { + guard let pageType = pageType else { return false } + return viewControllers.contains { controller in + return (controller as? MVMCoreViewControllerProtocol)?.pageType == pageType + } + } + + open func newDataReceived(in viewController: UIViewController) { + if viewController == self.viewController { + hideNavigationBarLine(for: viewController) + } + manager?.newDataReceived?(in: viewController) + } + + public func willDisplay(_ viewController: UIViewController) { + hideNavigationBarLine(for: viewController) + manager?.willDisplay?(viewController) + } + + public func displayedViewController(_ viewController: UIViewController) { + manager?.displayedViewController?(viewController) + } + + // MARK: - MVMCoreUISwipeNavigationProtocol + + public func swipeLeft() { + guard tabs.selectedIndex < (tabs.tabsModel?.tabs.count ?? 0) - 1 else { return } + _ = shouldSelectItem(IndexPath(row: tabs.selectedIndex + 1, section: 0), tabs: tabs) + } + + public func swipeRight() { + guard tabs.selectedIndex != 0 else { return } + _ = shouldSelectItem(IndexPath(row: tabs.selectedIndex - 1, section: 0), tabs: tabs) + } + + public func update(percentage: CGFloat) { + guard customInteractor?.interactive == true, + let index = index else { return } + tabs.progress(from: tabs.selectedIndex, toIndex: index, percentage: percentage) + } +} diff --git a/MVMCoreUI/Managers/SubNav/SubNavManagerNavigationController.swift b/MVMCoreUI/Managers/SubNav/SubNavManagerNavigationController.swift new file mode 100644 index 00000000..eb9dc5bd --- /dev/null +++ b/MVMCoreUI/Managers/SubNav/SubNavManagerNavigationController.swift @@ -0,0 +1,38 @@ +// +// SubNavManagerNavigationController.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 11/1/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation + +/// The navigation controller that the tabbarpagecontrol uses for the children controllers. It always has the navigation bar hidden. +@objc public class SubNavManagerNavigationController: UINavigationController { + + public override init(rootViewController: UIViewController) { + super.init(rootViewController: rootViewController) + setNavigationBarHidden(true, animated: false) + } + + public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + setNavigationBarHidden(true, animated: false) + } + + public override init(navigationBarClass: AnyClass?, toolbarClass: AnyClass?) { + super.init(navigationBarClass: navigationBarClass, toolbarClass: toolbarClass) + setNavigationBarHidden(true, animated: false) + } + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setNavigationBarHidden(true, animated: false) + } + + public override func setNavigationBarHidden(_ hidden: Bool, animated: Bool) { + guard hidden else { return } + super.setNavigationBarHidden(hidden, animated: animated) + } +} diff --git a/MVMCoreUI/Managers/SubNav/SubNavSwipeAnimator.swift b/MVMCoreUI/Managers/SubNav/SubNavSwipeAnimator.swift new file mode 100644 index 00000000..810dd4a5 --- /dev/null +++ b/MVMCoreUI/Managers/SubNav/SubNavSwipeAnimator.swift @@ -0,0 +1,60 @@ +// +// SubNavSwipeAnimator.swift +// MobileFirstFramework +// +// Created by Matt Bruce on 10/22/21. +// Copyright © 2021 Verizon Wireless. All rights reserved. +// + +import Foundation +import MVMCore + +@objcMembers public class SubNavSwipeAnimator: NSObject, MVMCoreViewControllerAnimatedTransitioning { + @objc dynamic public var interactiveTransitionCanceled: Bool = false + + private var animationTime: TimeInterval = 0.0 + private var direction: UISwipeGestureRecognizer.Direction + private weak var controller: UIViewController? + + public init(swipingDirection direction: UISwipeGestureRecognizer.Direction, controller: UIViewController) { + self.direction = direction + self.controller = controller + super.init() + } + + public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + self.animationTime = 0.5 + return self.animationTime + } + + public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + self.controller?.view.isUserInteractionEnabled = false + let containerView = transitionContext.containerView + + guard let originalViewController = transitionContext.viewController(forKey: .from), + let destinationViewController = transitionContext.viewController(forKey: .to) else { return } + + var destStartFrame = containerView.bounds + destStartFrame.origin.x = self.direction == .right ? -destStartFrame.size.width : destStartFrame.size.width + containerView.addSubview(destinationViewController.view) + destinationViewController.view.frame = destStartFrame + + let destEndFrame = containerView.bounds + var origEndFrame = containerView.bounds + origEndFrame.origin.x = self.direction == .right ? origEndFrame.size.width : -origEndFrame.size.width + + UIView.animate(withDuration: animationTime, animations: { + originalViewController.view.frame = origEndFrame + destinationViewController.view.frame = destEndFrame + }) { finished in + transitionContext.completeTransition(!transitionContext.transitionWasCancelled) + } + } + + public func animationEnded(_ transitionCompleted: Bool) { + if !transitionCompleted { + self.interactiveTransitionCanceled = true + } + self.controller?.view.isUserInteractionEnabled = true + } +} diff --git a/MVMCoreUI/OtherHandlers/CoreUIObject.swift b/MVMCoreUI/OtherHandlers/CoreUIObject.swift index d61d7be4..d44b49ac 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIObject.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIObject.swift @@ -16,7 +16,7 @@ import UIKit loadHandler = MVMCoreLoadHandler() cache = MVMCoreCache() sessionHandler = MVMCoreSessionTimeHandler() - actionHandler = MVMCoreActionHandler() + actionHandler = MVMCoreUIActionHandler() session = MVMCoreUISession() viewControllerMapping = MVMCoreUIViewControllerMappingObject() loggingDelegate = MVMCoreUILoggingHandler()