How to use scene cooldinator's pop() function when pops the top view controller from the navigation stack?

Page 384 note that It’s important that you always call the scene coordinator’s transition(to:type:) and pop() functions to transition between scenes.

So, when I push a view controller, the navigation controller will create a back bar button item for users to pop back, and its action can’t be changed. In this case, I create a left bar button item for the new top-level view controller along with a action calls the scene cooldinator’s pop function.

What’s weird is, when I clicked the left bar button item as soon as the new top-level controller appear which leads to the fatalError("Can't navigate back from \(currentViewController)").

In the end, how and when to use scene cooldinator’s pop() function to pop back to the previous view controller?

You have to set currentViewController correctly after push/pop using scene coordinator.
However, I always met crashes when push/pop quickly while using UIButton as the custom view of UIBarButtonItem .
It seems like that currentViewController can not be set correctly in the situation above.

What I wound up doing was adding a back scene coordinator method and call that from the view controllers viewWillDisappear method:

    @discardableResult
func back() -> Observable<Void> {
    let subject = PublishSubject<Void>()
    
    guard let navigationController = currentViewController.navigationController else {
        fatalError("No navigation controller: can't navigate back from \(currentViewController)")
    }

    // one-off subscription to be notified when back complete
    if currentNavDidShowObserver != nil {
        currentNavDidShowObserver!.dispose()
    }
    currentNavDidShowObserver = navigationController.rx.delegate
        .sentMessage(#selector(UINavigationControllerDelegate.navigationController(_:didShow:animated:)))
        .map { _ in }
        .bind(to: subject)

    currentViewController = SceneCoordinatorImpl.actualViewController(for: navigationController.viewControllers.last!)

    return subject.asObservable()
        .take(1)
        .ignoreElements()
}

Also note I added a currentNavDidShowObserver var to the scene coordinator otherwise all those supposedly “one-off” subscriptions really aren’t (just add a debug() after the sentMessage to see.) I changed all the other call sites as well.

@noahblues quick button presses can be alleviated by using an Action. Since the action stays “locked” while executing, all you have to do is delay the end of execution to prevent double taps. Hence some of the examples in the book where I do:

backButton.rx.action = viewModel.backAction()

and the back action is something like that:

func backAction() -> CocoaAction {
  return CocoaAction { [unowned self] in
    self.coordinator.pop()
    return Observable.empty()
      .delaySubscription(0.5, scheduler: MainScheduler.instance)
  }
}
1 Like

Unfortunately, it’s not work…Still push, pop,push,pop,push,crash…

Ok so there was a discussion about this on the RxSwift slack. It turns out that enabling a custom back button before the view controller is completely on-screen (therefore before the transition has completed) would lead to a crash when trying to popViewController early, even through the VC stack shows the new VC as present.

This looks more like a general iOS-related issue than a RxSwift one. What you may want to do is add your custom left bar button item but not immediately add the action to it. Rather, set the action in your viewDidAppear callback.

Awesome :+1: . Set the action in viewDidAppear works. Thank you very much.

2 Likes