jdisho / Papr

🌁 An Unsplash app for iOS

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

SceneCoordinator Transition

serjooo opened this issue · comments

First of all I would like to thank you for this amazing project. Truly your project has become an educational resource for me!

My question is about the SceneCoordinator. I implemented the Coordinator with the same logic without RX, because I don't have the enough confidence to start using RX in projects for production. I have only one missing link to intercept when the View Controllers pop or a tab bar item is changed you make the SceneCoordinator the navigationControllerDelegate and tabBarControllerDelegate. I am wondering what the difference between this:

currentViewController.navigationController?.rx.delegate.setForwardToDelegate(self, retainDelegate: false)
currentViewController.tabBarController?.rx.delegate.setForwardToDelegate(self, retainDelegate: false)

And this code is:

currentViewController.navigationController?.delegate = self
currentViewController.tabBarController?.delegate = self

I'm guessing RX has a fancy way of preventing memory leaks in this case. How can I make my own implementation of setForwardToDelegate? Again thank you very much!

Thanks for raising this as an issue 😊 @serjooo

Actually you don't need to implement setForwardToDelegate by your own. It is not necessary.

The following non-Rx implantation of SceneCoordiantor should work.

class SceneCoordinator: NSObject, SceneCoordinatorType {
    
    static var shared: SceneCoordinator!
    
    fileprivate var window: UIWindow
    fileprivate var currentViewController: UIViewController {
        didSet {
            currentViewController.tabBarController?.delegate = self
        }
    }
    
    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!
        }
        return viewController
    }
    
    func transition(to scene: TargetScene) {
        switch scene.transition {
        case let .tabBar(tabBarController):
            guard let selectedViewController = tabBarController.selectedViewController else {
               fatalError("Selected view controller doesn't exists")
            }
            currentViewController = SceneCoordinator.actualViewController(for: selectedViewController)
            window.rootViewController = tabBarController
        case let .root(viewController):
            currentViewController = SceneCoordinator.actualViewController(for: viewController)
            window.rootViewController = viewController
        case let .push(viewController):
            guard let navigationController = currentViewController.navigationController else {
                fatalError("Can't push a view controller without a current navigation controller")
            }
            navigationController.pushViewController(SceneCoordinator.actualViewController(for: viewController), animated: true)
        case let .present(viewController):
            currentViewController.present(viewController, animated: true)
            currentViewController = SceneCoordinator.actualViewController(for: viewController)
        case let .alert(viewController):
            currentViewController.present(viewController, animated: true)
        }
    }
    
    func pop(animated: Bool) {
        if let presentingViewController = currentViewController.presentingViewController {
            currentViewController.dismiss(animated: animated) {
                self.currentViewController = SceneCoordinator.actualViewController(for: presentingViewController)
            }
        } else if let navigationController = currentViewController.navigationController {
            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)")
        }
    }
}

// MARK: - UITabBarControllerDelegate

extension SceneCoordinator: UITabBarControllerDelegate {
    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController)  {
        currentViewController = SceneCoordinator.actualViewController(for: viewController)
    }
}

I am closing this issue for now. However, feel free to reopen it if it's necessary.

@jdisho That's exactly what I have! I really love that you thought about the coordinator pattern as a Universal Coordinator instead of really having to write a coordinator for each Controller. I really did not see the benefit of that. This solution is really clear, does the job, and removes a lot of boilerplate code! Good job! Again thank you!

@jdisho I might have found a bug and here is the fix for it. While Presenting Modally from a selected UITabBarController then dismissing the Controller, the actualViewController doesn't return the UITabBarController selectedViewController as the actualViewController. It instead returns the UITabBarController, so trying to Push another Controller while staying on the same Tab would crash because the fatalError.

I did this to fix this bug:

static func actualViewController(for viewController: UIViewController) -> UIViewController {
     var controller = viewController
     if let tabBarController = controller as? UITabBarController {
         guard let selectedViewController = tabBarController.selectedViewController else {
             return tabBarController
         }
         controller = selectedViewController
     }
     if let navigationController = controller as? UINavigationController {
         return navigationController.viewControllers.first!
     }
     return viewController
 } 

@serjooo Nice, thank you 👍
Can you create a PR regarding this fix?

@jdisho will do!