Created
April 13, 2019 13:29
-
-
Save DanielCardonaRojas/c9cfee92262e10df3d93a206d00e2eed to your computer and use it in GitHub Desktop.
Base protocol for adopting Coordinator pattern.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import Foundation | |
protocol Coordinator: class { | |
typealias ExitHandler = (Coordinator) -> Void | |
var childCoordinators: [Coordinator] { get set } | |
var navigationController: UINavigationController { get set } | |
var initialViewController: UIViewController? { get } | |
func willNavigate(from: UIViewController, to: UIViewController) | |
func start() | |
var exit: ExitHandler? { get set } | |
} | |
// MARK: - Default implementations | |
extension Coordinator { | |
func willNavigate(from: UIViewController, to: UIViewController) { | |
} | |
} | |
// MARK: - Navigation Helpers | |
extension Coordinator { | |
func end() { | |
exit?(self) | |
} | |
func push(_ viewController: UIViewController, animated: Bool) { | |
viewController.onViewDidAppear { | |
let vcType = String(describing: type(of: viewController)) | |
let presentingCoordinator = String(describing: Self.self) | |
log.debug("[\(presentingCoordinator)] \(vcType) -> viewDidAppear", type: .coordinator, tag: .none) | |
} | |
navigationController.pushViewController(viewController, animated: animated) | |
} | |
func pop(animated: Bool, completion: (() -> Void)? = nil) { | |
navigationController.popViewController(animated: animated, completion: completion) | |
} | |
func present(_ viewController: UIViewController, animated: Bool, completion: (() -> Void)?) { | |
if !(viewController is UINavigationController) { | |
viewController.onViewDidAppear { | |
let vcType = String(describing: type(of: viewController)) | |
let presentingCoordinator = String(describing: Self.self) | |
log.debug("[\(presentingCoordinator)] \(vcType) -> viewDidAppear", type: .coordinator, tag: .none) | |
} | |
} | |
navigationController.topViewController?.present(viewController, animated: animated, completion: completion) | |
} | |
func startChild(_ coordinator: Coordinator, modal: Bool, animated: Bool, onExit: ExitHandler?) { | |
// Root coordinator is the delegate for all navigationControllers | |
coordinator.navigationController.delegate = navigationController.delegate | |
coordinator.exit = onExit | |
coordinator.start() | |
if modal { | |
present(coordinator.navigationController, animated: true, completion: nil) | |
} | |
childCoordinators.append(coordinator) | |
} | |
func dismiss(animated: Bool, completion: (() -> Void)?) { | |
navigationController.visibleViewController?.dismiss(animated: animated, completion: completion) | |
} | |
} | |
// MARK: - Child coordinator inspection | |
extension Coordinator { | |
var isActive: Bool { | |
return currentCoordinator === self | |
} | |
var currentCoordinator: Coordinator { | |
return childCoordinators.last?.currentCoordinator ?? childCoordinators.last ?? self | |
} | |
func removeChildCoordinator<T>(ofType: T.Type) { | |
for c in childCoordinators { | |
if c is T { | |
remove(coordinator: c) | |
return | |
} | |
c.removeChildCoordinator(ofType: T.self) | |
} | |
} | |
func remove(coordinator: Coordinator) { | |
for (offset, c) in childCoordinators.enumerated() { | |
if c === coordinator { | |
childCoordinators.remove(at: offset) | |
return | |
} | |
c.remove(coordinator: coordinator) | |
} | |
} | |
var allChildCoordinators: [Coordinator] { | |
var result = [Coordinator]() | |
for c in childCoordinators { | |
result += c.allChildCoordinators | |
} | |
return result + childCoordinators | |
} | |
func childCoordinator<T: Coordinator>(ofType: T.Type) -> T? { | |
for c in childCoordinators { | |
return (c as? T) ?? c.childCoordinator(ofType: T.self) | |
} | |
return nil | |
} | |
} | |
internal extension Coordinator { | |
static func createNavigationController() -> UINavigationController { | |
let navigation = UINavigationController() | |
navigation.isNavigationBarHidden = true | |
navigation.delegate = (UIApplication.shared.delegate as? AppDelegate)?.navigationCoordinator?.navigationController.delegate | |
return navigation | |
} | |
} | |
extension UINavigationController { | |
func pushViewController(_ viewController: UIViewController, animated: Bool = true, completion: (() -> Void)?) { | |
CATransaction.begin() | |
CATransaction.setCompletionBlock(completion) | |
pushViewController(viewController, animated: animated) | |
CATransaction.commit() | |
} | |
func popViewController(animated: Bool, completion: (() -> Void)?) { | |
CATransaction.begin() | |
CATransaction.setCompletionBlock(completion) | |
popViewController(animated: animated) | |
CATransaction.commit() | |
} | |
func popToRootViewController(animated: Bool, completion: (() -> Void)?) { | |
CATransaction.begin() | |
CATransaction.setCompletionBlock(completion) | |
popToRootViewController(animated: animated) | |
CATransaction.commit() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note this is relying on an additional extension for logging presented viewcontrollers lifecycle.
Also initialViewController could be omitted.