Skip to content

Instantly share code, notes, and snippets.

@DanielCardonaRojas
Created April 13, 2019 13:29
Show Gist options
  • Save DanielCardonaRojas/c9cfee92262e10df3d93a206d00e2eed to your computer and use it in GitHub Desktop.
Save DanielCardonaRojas/c9cfee92262e10df3d93a206d00e2eed to your computer and use it in GitHub Desktop.
Base protocol for adopting Coordinator pattern.
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()
}
}
@DanielCardonaRojas
Copy link
Author

Note this is relying on an additional extension for logging presented viewcontrollers lifecycle.

Also initialViewController could be omitted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment