Skip to content

Instantly share code, notes, and snippets.

@Herakleis
Created August 19, 2017 16:31
Show Gist options
  • Save Herakleis/9ca4676b1f1496553bd5d0fd93145889 to your computer and use it in GitHub Desktop.
Save Herakleis/9ca4676b1f1496553bd5d0fd93145889 to your computer and use it in GitHub Desktop.
SceneCoordinator
import UIKit
import RxSwift
import RxCocoa
final class SceneCoordinator: SceneCoordinatorType {
fileprivate var window: UIWindow
var currentViewController: UIViewController
required init(window: UIWindow) {
self.window = window
currentViewController = window.rootViewController!
}
static func actualViewController(for viewController: UIViewController) -> UIViewController {
if let navigationController = viewController as? UINavigationController {
return navigationController.viewControllers.first!
} else {
return viewController
}
}
@discardableResult
func transition(to scene: Scene, type: SceneTransitionType) -> Observable<Void> {
let subject = PublishSubject<Void>()
let viewController = scene.viewController()
switch type {
case .root:
currentViewController = SceneCoordinator.actualViewController(for: viewController)
window.rootViewController = viewController
subject.onCompleted()
case .push(let animated):
guard let navigationController = currentViewController.navigationController else {
fatalError("Can't push a view controller without a current navigation controller")
}
// one-off subscription to be notified when push complete
_ = navigationController.rx.delegate
.sentMessage(#selector(UINavigationControllerDelegate.navigationController(_:didShow:animated:)))
.map { _ in }
.bind(to: subject)
navigationController.pushViewController(viewController, animated: animated)
currentViewController = SceneCoordinator.actualViewController(for: viewController)
case .modal(let animated):
currentViewController.present(viewController, animated: animated) {
subject.onCompleted()
}
currentViewController = SceneCoordinator.actualViewController(for: viewController)
case .pushToVC(let stack, let animated):
guard let navigationController = currentViewController.navigationController else {
fatalError("Can't push a view controller without a current navigation controller")
}
var controllers = navigationController.viewControllers
stack.forEach { controllers.append($0) }
controllers.append(viewController)
// one-off subscription to be notified when push complete
_ = navigationController.rx.delegate
.sentMessage(#selector(UINavigationControllerDelegate.navigationController(_:didShow:animated:)))
.map { _ in }
.bind(to: subject)
navigationController.setViewControllers(controllers, animated: animated)
currentViewController = SceneCoordinator.actualViewController(for: viewController)
default:
break
}
return subject.asObservable()
.take(1)
.ignoreElements()
}
@discardableResult
func pop(animated: Bool) -> Observable<Void> {
let subject = PublishSubject<Void>()
if let presenter = currentViewController.presentingViewController {
// dismiss a modal controller
currentViewController.dismiss(animated: animated) {
self.currentViewController = SceneCoordinator.actualViewController(for: presenter)
subject.onCompleted()
}
} else if let navigationController = currentViewController.navigationController {
// navigate up the stack
// one-off subscription to be notified when pop complete
_ = navigationController.rx.delegate
.sentMessage(#selector(UINavigationControllerDelegate.navigationController(_:didShow:animated:)))
.take(1) // To delete if already in return at bottom
.map { _ in }
.bind(to: subject)
guard navigationController.popViewController(animated: animated) != nil else {
fatalError("can't navigate back from \(currentViewController)")
}
currentViewController = SceneCoordinator.actualViewController(for: navigationController.viewControllers.last!)
} else {
fatalError("Not a modal, no navigation controller: can't navigate back from \(currentViewController)")
}
return subject.asObservable()
.take(1)
.ignoreElements()
}
@discardableResult
func popToRoot(animated: Bool) -> Observable<Void> {
let subject = PublishSubject<Void>()
if let navigationController = currentViewController.navigationController {
// navigate up the stack
// one-off subscription to be notified when pop complete
_ = navigationController.rx.delegate
.sentMessage(#selector(UINavigationControllerDelegate.navigationController(_:didShow:animated:)))
.take(1) // To delete if already in return at bottom
.map { _ in }
.bind(to: subject)
guard navigationController.popToRootViewController(animated: animated) != nil else {
fatalError("can't navigate back to root VC from \(currentViewController)")
}
currentViewController = SceneCoordinator.actualViewController(for: navigationController.viewControllers.first!)
}
return subject.asObservable()
.take(1)
.ignoreElements()
}
@discardableResult
func popToVC(_ viewController: UIViewController, animated: Bool) -> Observable<Void> {
let subject = PublishSubject<Void>()
if let navigationController = currentViewController.navigationController {
// navigate up the stack
// one-off subscription to be notified when pop complete
_ = navigationController.rx.delegate
.sentMessage(#selector(UINavigationControllerDelegate.navigationController(_:didShow:animated:)))
.take(1) // To delete if already in return at bottom
.map { _ in }
.bind(to: subject)
guard navigationController.popToViewController(viewController, animated: animated) != nil else {
fatalError("can't navigate back to VC from \(currentViewController)")
}
currentViewController = SceneCoordinator.actualViewController(for: navigationController.viewControllers.last!)
}
return subject.asObservable()
.take(1)
.ignoreElements()
}
}
@leeprobert
Copy link

I get a compile error when using this class:
Cannot convert return expression of type 'Completable' (aka 'PrimitiveSequence<CompletableTrait, Never>') to return type 'Observable<Void>'
I get this in any of the functions that return the Subject as an Observable:
return subject.asObservable() .take(1) .ignoreElements()

@H3ld
Copy link

H3ld commented Sep 18, 2018

Hello Github!

My very first Post here :)

@leeprobert , I didn't tested it, but wouldn't just following work too?

return subject
    .asObservable()
    .take(1)
    .filter { return false }

@RaoulNL
Copy link

RaoulNL commented Apr 8, 2019

I changed the result of the method (which was Observable) to Completable.

@Andy0570
Copy link

By the way, If you set function return type from Observable<Void> to Completable, you will see Xcode compiler error.

Compiler error

Cannot convert return expression of type 'Observable<Never>' to return type 'Completable' (aka 'PrimitiveSequence<CompletableTrait, Never>')

Solution

return subject.asObservable()
            .take(1)
            .ignoreElements()
            .asCompletable() // Add This Operator.

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