nimblehq / VIPER-Templates

Xcode files templates for various parts of VIPER module
5 stars 1 forks source link

Remove View Input out of Router #19

Open teamdisc opened 4 years ago

teamdisc commented 4 years ago

Also as we discussed that we would like to update our template to remove ViewInput out of our Router template.

What we currently have:

final class SomeRouter {

    weak var view: SomeViewInput?

    private var viewController: UIViewController? {
        return view as? UIViewController
    }
}

// MARK: - SomeRouterInput

extension SomeRouter: SomeRouterInput {

    func showScreenA() {
        viewController?.navigationController?.push(UIViewController(), animated: true)
        // or viewController?.show(UIViewController(), sender: nil)
    }

    func showScreenB() {
        viewController?.present(UIViewController(), animated: true)
    }
}

The issue we have with this one is that our Router becomes not really reusable at all because the navigation logic is tied up with ViewInput, or SomeViewInput in the case above.

Even if we try to achieve it with generic, we would eventually face an issue with generic requirement for concrete class as we couldn't do something like SomeRouter<SomeViewInput> and SomeRouter<AnotherViewInput> since T with generic constraint needs to be a concrete class.

I think the new syntax some for opaque return type might solve the issue also, but as we all know, it would only support for iOS 13 onwards. So not really applicable in our case until the next 3 years I suppose šŸ¤”

So here is what I'd like to propose:

final class SomeRouter {

    private(set) weak var navigator: Navigatable?

    init(navigator: Navigator) {
        self.navigator = navigator
    }
}

// MARK: - SomeRouterInput

extension SomeRouter: SomeRouterInput {

    func showScreenA() {
        navigator?.push(UIViewController())
    }

    func showScreenB() {
        navigator?.present(UIViewController())
    }
}

The tweaks in Module where we composite them all would look pretty much like this

final class SomeModule {

    // its properties ...

    init() {
        view = SomeViewController()
        router = SomeRouter(navigator: view)
        ...
    }
}

Also, small note here for other navigation types like Tab navigation or submodule navigation, we will also need to implement it case by case and inject it when compositing stuffs in Module.

Still, there are a couple of things that we need to find a solution before implementing this in our template.

First, this would require any project using the template to have this below implemented. So this means that it'll introduce some dependency for the template. I still haven't come up with a good idea how to deal with this yet. So any suggestions would be really appreciated.

protocol Navigatable: AnyObject {

    func push(_ viewController: UIViewController)
    func present(_ viewController: UIViewController)
}

extension UIViewController: Navigatable {

    func push(_ viewController: UIViewController) {
        navigationController?.pushViewController(viewController, animated: true)
    }

    func present(_ viewController: UIViewController) {
        present(viewController, animated: true)
    }
}

And another thing is that we might need to name the Navigatable protocol and navigator better? šŸ¤”

Let's discuss here and feel free to object as I might have missed on some certain points šŸ˜†