Skip to content

Commit

Permalink
Remove fromViewController, it's irrelevant for what the code proclaim…
Browse files Browse the repository at this point in the history
… to do:

handlePopBack (by Customer through backButtonItem) to some UIVC instance and giving a chance to the subclass to do something about it.

Added lots of comments and split the file into groups using MARK-ing
  • Loading branch information
Aleksandar Vacic committed Jan 25, 2019
1 parent 66ecd11 commit 09b040f
Showing 1 changed file with 54 additions and 32 deletions.
86 changes: 54 additions & 32 deletions Coordinator/NavigationCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,25 @@ open class NavigationCoordinator: Coordinator<UINavigationController>, UINavigat
// References to actual UIViewControllers managed by this Coordinator instance.
open var viewControllers: [UIViewController] = []

/// This method is implemented to detect when "pop" happens.
/// `popViewController` must be detected in order to remove popped VC from Coordinator's `viewControllers` array.
/// This method is implemented to detect when customer "pop" back using UINC's backButtonItem.
/// (Need to detect that in order to remove popped VC from Coordinator's `viewControllers` array.)
///
/// It is strongly advised to *not* override this method, but it's allowed to do so in case you really need to.
/// What you likely want to override is `handlePopBack(to:)` method.
open func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
let fromViewController = navigationController.transitionCoordinator?.viewController(forKey: .from)
self.didShowController(viewController, fromViewController: fromViewController)
self.didShowController(viewController)
}

/// If you subclass NavigationCoordinator, then override this method if you need to
/// do something special when customer taps the UIKit's backButton in the navigationBar.
///
/// By default, this does nothing.
open func handlePopBack(to vc: UIViewController?) {
}


// MARK:- Presenting

public func present(_ vc: UIViewController) {
rootViewController.present(vc, animated: true, completion: nil)
}
Expand All @@ -30,6 +39,9 @@ open class NavigationCoordinator: Coordinator<UINavigationController>, UINavigat
rootViewController.dismiss(animated: true, completion: nil)
}


// MARK:- Navigating

/// Main method to push supplied UIVC to the navigation stack.
/// First it adds the `vc` to the Coordinator's `viewControllers` then calls `show(vc)` on the root.
public func show(_ vc: UIViewController) {
Expand Down Expand Up @@ -68,12 +80,8 @@ open class NavigationCoordinator: Coordinator<UINavigationController>, UINavigat
rootViewController.popToViewController(vc, animated: animated)
}

/// If you subclass NavigationCoordinator, then override this method if you need to
/// do something special when customer taps the UIKit's backButton in the navigationBar.
///
/// By default, this does nothing.
open func handlePopBack(to vc: UIViewController?) {
}

// MARK:- Coordinator lifecycle

open override func start(with completion: @escaping () -> Void) {
// assign itself as UINavigationControllerDelegate
Expand Down Expand Up @@ -108,33 +116,47 @@ open class NavigationCoordinator: Coordinator<UINavigationController>, UINavigat
}

private extension NavigationCoordinator {
func didShowController(_ viewController: UIViewController, fromViewController: UIViewController?) {
if let fromViewController = fromViewController {
guard viewControllers.contains(fromViewController) else { return }
guard let last = viewControllers.last, last === fromViewController else { return }

if let index = viewControllers.firstIndex(of: viewController) {
let lastPosition = viewControllers.count - 1
viewControllers = Array(viewControllers.dropLast(lastPosition - index))
handlePopBack(to: viewController)
} else {
viewControllers.removeLast()
handlePopBack(to: last)
}
} else {
guard viewController !== viewControllers.last else { return }
guard let index = viewControllers.firstIndex(of: viewController) else { return }

let lastPosition = viewControllers.count - 1
viewControllers = Array(viewControllers.dropLast(lastPosition - index))
handlePopBack(to: viewController)
}
func didShowController(_ viewController: UIViewController) {
// Note: various sanity checks are done below, explained in comments.
// You would want to add some log calls for each one
// (or at least add a breakpoint so you know when it happens)
// since those checks point to logical errors in the app's architecture flows.


// If VC, which was just shown, is the last in this Coordinator's stack,
// then just bail out, because popped VC was not in this Coordinator's domain.
// | If this actually happens, it likely points to a mistake somewhere else.
// | (It means we had some `show(vc)` happen that _did not_ update this Coordinator's viewControllers,
// | nor it switched to some other Coordinator which should have become UINC.delegate)
if viewController === viewControllers.last {
return
}

// Check: just shown VC should be present in Coordinator's viewControllers sequence.
// If it's not there, then bail out.
// | Again, this should not happen,
// | since this Coordinator should then not be UINCDelegate.
guard let index = viewControllers.firstIndex(of: viewController) else {
return
}

// Note: using firstIndex(of:) and not .last nicely
// handles if you programatically pop more than one UIVC.


let lastIndex = viewControllers.count - 1
if lastIndex <= index {
return
}
viewControllers = Array(viewControllers.dropLast(lastIndex - index))
handlePopBack(to: viewController)



// is there any controller left shown in this Coordinator?
if viewControllers.count == 0 {
// inform the parent Coordinator that this child Coordinator has no more VCs
parent?.coordinatorDidFinish(self, completion: {})
return
}
}
}

0 comments on commit 09b040f

Please sign in to comment.