Skip to content

Instantly share code, notes, and snippets.

@mcousillas6
Created June 13, 2019 17:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mcousillas6/c40b09e47efb33a7b62962c83c248d1c to your computer and use it in GitHub Desktop.
Save mcousillas6/c40b09e47efb33a7b62962c83c248d1c to your computer and use it in GitHub Desktop.
SwiftNavigator
//
// Navigator.swift
// SwiftNavigator
//
// Created by Mauricio Cousillas on 02/23/19.
// Copyright © 2019 Mauricio Cousillas. All rights reserved.
//
import Foundation
import UIKit
/**
The base navigator class implements the navigator protocol
exposing basic behaviour extensible via inheritance.
For example, you should subclass if you want to add
some logic to the initial route by checking something
stored in your keychain.
*/
open class BaseNavigator: Navigator {
open var rootViewController: UINavigationController?
open var currentViewController: UIViewController?
public required init(with route: Route) {
let screen = route.screen
if let navigation = screen as? UINavigationController {
rootViewController = navigation
} else {
let viewController = screen
rootViewController = UINavigationController(rootViewController: viewController)
rootViewController?.isNavigationBarHidden = true
currentViewController = viewController
}
}
}
/**
The Navigator is the base of this framework, it handles all
your application navigation stack.
It keeps track of your root NavigationController and the
ViewController that is currently displayed. This way it can
handle any kind of navigation action that you might want to dispatch.
*/
public protocol Navigator: class {
/// The root navigation controller of your stack.
var rootViewController: UINavigationController? { get set }
/// The currently visible ViewController
var currentViewController: UIViewController? { get set }
/// Convencience init to set your application starting screen.
init(with route: Route)
/**
Navigate from your current screen to a new route.
- Parameters:
- route: The destination route of your navigation action.
- transition: The transition type that you want to use.
- animated: Animate the transition or not.
- completion: Completion handler.
*/
func navigate(to route: Route, with transition: TransitionType, animated: Bool, completion: (() -> Void)?)
/**
Navigate from your current screen to a new entire navigator. Can only push a router as a modal. Afterwards, other controllers can be pushed inside the presented Navigator.
- Parameters:
- Navigator: The destination navigator that you want to navigate to
- animated: Animate the transition or not.
- completion: Completion handler.
*/
func navigate(to router: Navigator, animated: Bool, completion: (() -> Void)?)
/**
Handles backwards navigation through the stack.
*/
func pop(animated: Bool)
/**
Handles backwards navigation through the stack.
- Parameters:
- route: The index of the route of your navigation action.
- animated: Animate the transition or not.
*/
func popTo(index: Int, animated: Bool)
/**
Handles backwards navigation through the stack.
- Parameters:
- route: The destination route of your navigation action.
- animated: Animate the transition or not.
*/
func popTo(route: Route, animated: Bool)
/**
Dismiss your current ViewController.
- Parameters:
- animated: Animate the transition or not.
- completion: Completion handler.
*/
func dismiss(animated: Bool, completion: (() -> Void)?)
}
public extension Navigator {
public func navigate(to route: Route, with transition: TransitionType, animated: Bool = true, completion: (() -> Void)? = nil) {
let viewController = route.screen
switch transition {
case .modal:
currentViewController?.present(viewController, animated: animated, completion: completion)
currentViewController = viewController
case .push:
rootViewController?.pushViewController(viewController, animated: animated)
currentViewController = viewController
case .reset:
rootViewController?.setViewControllers([viewController], animated: animated)
currentViewController = viewController
case .changeRoot:
UIView.animate(withDuration: 0.3) { [weak self] in
if let navigation = (viewController as? UINavigationController) {
UIApplication.shared.keyWindow?.rootViewController = navigation
self?.rootViewController = navigation
self?.currentViewController = self?.rootViewController?.topViewController
} else {
let navigationController = UINavigationController(rootViewController: viewController)
UIApplication.shared.keyWindow?.rootViewController = navigationController
self?.rootViewController = navigationController
self?.rootViewController?.isNavigationBarHidden = true
self?.currentViewController = viewController
}
}
}
}
func navigate(to router: Navigator, animated: Bool, completion: (() -> Void)?) {
guard let viewController = router.rootViewController else {
assert(false, "Navigator does not have a root view controller")
return
}
currentViewController?.present(viewController, animated: animated, completion: completion)
currentViewController = viewController
}
public func pop(animated: Bool = true) {
rootViewController?.popViewController(animated: animated)
currentViewController = rootViewController?.visibleViewController
}
public func popToRoot(animated: Bool = true) {
rootViewController?.popToRootViewController(animated: animated)
}
public func popTo(index: Int, animated: Bool = true) {
guard
let viewControllers = rootViewController?.viewControllers,
viewControllers.count > index
else { return }
let viewController = viewControllers[index]
rootViewController?.popToViewController(viewController, animated: animated)
currentViewController = viewController
}
public func popTo(route: Route, animated: Bool = true) {
guard
let viewControllers = rootViewController?.viewControllers,
let viewController = viewControllers.first(where: {
type(of: $0) == type(of: route.screen)
})
else { return }
rootViewController?.popToViewController(viewController, animated: true)
currentViewController = viewController
}
public func dismiss(animated: Bool = true, completion: (() -> Void)? = nil) {
let presentingViewController = currentViewController?.presentingViewController
currentViewController?.dismiss(animated: animated, completion: completion)
currentViewController = presentingViewController
}
}
/**
Protocol used to define a Route. The route contains
all the information necessary to instantiate it's screen.
For example, you could have a LoginRoute, that knows how
to instantiate it's viewModel, and also forward any
information that it's passed to the Route.
*/
public protocol Route {
// The screen that should be returned for that Route.
var screen: UIViewController { get }
}
/// Available Transition types for navigation actions.
public enum TransitionType {
/// Presents the screen modally on top of the current ViewController
case modal
/// Pushes the next screen to the rootViewController navigation Stack.
case push
/// Resets the rootViewController navitationStack and set's the Route's screen as the initial view controller of the stack.
case reset
/// Replaces the key window's Root view controller with the Route's screen.
case changeRoot
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment