Skip to content

Instantly share code, notes, and snippets.

@zvonicek
Created March 4, 2017 20:45
Show Gist options
  • Save zvonicek/434fd99a8a8916a95e0b1a10e5ff1d5b to your computer and use it in GitHub Desktop.
Save zvonicek/434fd99a8a8916a95e0b1a10e5ff1d5b to your computer and use it in GitHub Desktop.
import UIKit
protocol Coordinator {
/// Triggers navigation to the corresponding controller
func start()
/// Stops corresponding controller and returns back to previous one
func stop()
/// Called when segue navigation form corresponding controller to different controller is about to start and should handle this navigation
func navigate(from source: UIViewController, to destination: UIViewController, with identifier: String?, and sender: AnyObject?)
}
/// Navigate and stop methods are optional
extension Coordinator {
func navigate(from source: UIViewController, to destination: UIViewController, with identifier: String?, and sender: AnyObject?) {
}
func stop() {
}
}
protocol DefaultCoordinator: Coordinator {
associatedtype VC: UIViewController
weak var viewController: VC? { get set }
var animated: Bool { get }
weak var delegate: CoordinatorDelegate? { get }
}
protocol PushCoordinator: DefaultCoordinator {
var configuration: ((VC) -> ())? { get }
var navigationController: UINavigationController { get }
}
protocol ModalCoordinator: DefaultCoordinator {
var configuration: ((VC) -> ())? { get }
var navigationController: UINavigationController { get }
weak var destinationNavigationController: UINavigationController? { get }
}
enum PresentationStyle {
case push
case modal
}
protocol PushModalCoordinator: DefaultCoordinator {
var configuration: ((VC) -> ())? { get }
var navigationController: UINavigationController? { get }
var presentationStyle: PresentationStyle { get }
weak var destinationNavigationController: UINavigationController? { get }
}
extension DefaultCoordinator {
// default implementation if not overriden
var animated: Bool {
get {
return true
}
}
// default implementation of nil delegate, should be overriden when needed
weak var delegate: CoordinatorDelegate? {
get {
return nil
}
}
}
extension PushCoordinator where VC: UIViewController, VC: Coordinated {
func start() {
guard let viewController = viewController else {
return
}
configuration?(viewController)
viewController.setCoordinator(self)
navigationController.pushViewController(viewController, animated: animated)
}
func stop() {
delegate?.willStop(in: self)
navigationController.popViewController(animated: animated)
delegate?.didStop(in: self)
}
}
extension ModalCoordinator where VC: UIViewController, VC: Coordinated {
func start() {
guard let viewController = viewController else {
return
}
configuration?(viewController)
viewController.setCoordinator(self)
if let destinationNavigationController = destinationNavigationController {
// wrapper navigation controller given, present it
navigationController.present(destinationNavigationController, animated: animated, completion: nil)
} else {
// no wrapper navigation controller given, present actual controller
navigationController.present(viewController, animated: animated, completion: nil)
}
}
func stop() {
delegate?.willStop(in: self)
viewController?.dismiss(animated: true, completion: {
self.delegate?.didStop(in: self)
})
}
}
extension PushModalCoordinator where VC: UIViewController, VC: Coordinated {
// By default to distinguish between modal and push, a presence of destinationNavigationController is checked
// as this is a good heuristics (it's usually not desired to push another navigation controller). This behaviour
// can be redefined by redeclaring this property on any concrete PushModalCoordinator.
var presentationStyle: PresentationStyle {
return self.destinationNavigationController != nil ? .modal : .push
}
func start() {
guard let viewController = viewController else {
return
}
configuration?(viewController)
viewController.setCoordinator(self)
switch presentationStyle {
case .modal where destinationNavigationController != nil:
navigationController?.present(destinationNavigationController!, animated: animated, completion: nil)
case .push:
navigationController?.pushViewController(viewController, animated: animated)
default:
break
}
}
func stop() {
delegate?.willStop(in: self)
switch presentationStyle {
case .modal:
viewController?.dismiss(animated: true, completion: {
self.delegate?.didStop(in: self)
})
case .push:
let _ = navigationController?.popViewController(animated: animated)
delegate?.didStop(in: self)
}
}
}
protocol CoordinatorDelegate: class {
func willStop(in coordinator: Coordinator)
func didStop(in coordinator: Coordinator)
}
/// Used typically on view controllers to refer to it's coordinator
protocol Coordinated {
func getCoordinator() -> Coordinator?
func setCoordinator(_ coordinator: Coordinator)
}
class CoordinatorSegue: UIStoryboardSegue {
open var sender: AnyObject!
override func perform() {
guard let source = self.source as? Coordinated else {
return
}
source.getCoordinator()?.navigate(from: self.source, to: destination, with: identifier, and: sender)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment