sub nav manager
This commit is contained in:
parent
ad483e42c5
commit
ba16e241cb
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
144
MVMCoreUI/Managers/SubNav/SubNavInteractor.swift.swift
Normal file
144
MVMCoreUI/Managers/SubNav/SubNavInteractor.swift.swift
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
331
MVMCoreUI/Managers/SubNav/SubNavManagerController.swift
Normal file
331
MVMCoreUI/Managers/SubNav/SubNavManagerController.swift
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
60
MVMCoreUI/Managers/SubNav/SubNavSwipeAnimator.swift
Normal file
60
MVMCoreUI/Managers/SubNav/SubNavSwipeAnimator.swift
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,7 @@ import UIKit
|
||||
loadHandler = MVMCoreLoadHandler()
|
||||
cache = MVMCoreCache()
|
||||
sessionHandler = MVMCoreSessionTimeHandler()
|
||||
actionHandler = MVMCoreActionHandler()
|
||||
actionHandler = MVMCoreUIActionHandler()
|
||||
session = MVMCoreUISession()
|
||||
viewControllerMapping = MVMCoreUIViewControllerMappingObject()
|
||||
loggingDelegate = MVMCoreUILoggingHandler()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user