Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Coordinators and deep linking

rechsteiner
February 13, 2020

Coordinators and deep linking

How we handle navigation inside the mobile bank at DNB

rechsteiner

February 13, 2020
Tweet

More Decks by rechsteiner

Other Decks in Programming

Transcript

  1. • Coordinators are responsible for the presentation flow between view

    controllers. • View controllers have no knowledge about the context they are being used. The coordinator pattern protocol Coordinator: class { var childCoordinators: [Coordinator] { get set } }
  2. class AppCoordinator: Coordinator { func open(deepLink: DeepLink, animated: Bool) {

    if authService.isAuthenticated { tabBarCoordinator.open(deepLink: deepLink, animated: animated) } else { showAuthCoordinator() } } }
  3. enum DeepLink { case pay case transfer case pendingPayments case

    accountDetail(id: String) } protocol Coordinator: class { var childCoordinators: [Coordinator] { get set } func open(deepLink: DeepLink, animated: Bool) }
  4. Pay

  5. class AppCoordinator: Coordinator { ... func open(deepLink: DeepLink, animated: Bool)

    { if authService.isAuthenticated { tabBarCoordinator.open(deepLink: deepLink, animated: animated) } else { showAuthCoordinator() } } }
  6. Problem #1 • Each new deep link requires you to

    update the entire tree of coordinators
  7. Problem #2 • Need to manually remove references to coordinators

    when navigating backwards in a navigation controller
  8. extension Coordinator: UINavigationControllerDelegate { func navigationController( navigationController: UINavigationController, didShowViewController viewController:

    UIViewController, animated: Bool ) { guard let fromViewController = navigationController.transitionCoordinator?.viewController(forKey: .from), !navigationController.viewControllers.contains(fromViewController) else { return } if fromViewController is HomeViewController { homeCoordinator = nil } } } Back button problem
  9. Automatically deallocate coordinators open class NavigationCoordinator: NSObject, Coordinator, UINavigationControllerDelegate {

    weak var parentCoordinator: NavigationCoordinator? var rootViewController: UIViewController var navigationController: UINavigationController var childCoordinators: [Coordinator] var childViewControllers: [WeakRef<UIViewController>] init(rootViewController: UIViewController, navigationController: UINavigationController) { self.childCoordinators = [] self.childViewControllers = [] self.rootViewController = rootViewController self.navigationController = navigationController super.init() } ... public func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) { guard let toViewController = navigationController.transitionCoordinator?.viewController(forKey: .to) else { return } removeIfNeeded(coordinator: self, toViewController: toViewController) } private func contains(coordinator: NavigationCoordinator, viewController: UIViewController) -> Bool { return coordinator.childViewControllers.contains(where: { $0.value === viewController }) } func removeIfNeeded(coordinator: NavigationCoordinator, toViewController: UIViewController) { if contains(coordinator: coordinator, viewController: toViewController) { return } else if let parentCoordinator = coordinator.parentCoordinator { parentCoordinator.remove(coordinator: coordinator) parentCoordinator.set(delegate: parentCoordinator) removeIfNeededcoordinator: parentCoordinator, toViewController: toViewController)
  10. Example final class AccountDetailCoordinator: NavigationCoordinator { } func select() {

    let viewController = AccountConditionsViewController() viewController.coordinator = self push(viewController: viewController, animated: true) } func selectConditions() { push(coordinator: AccountDetailCoordinator(navigationController)) }
  11. Problem #3 • View controller could not use UINavigationControllerDelegate •

    Basically reimplementing stuff we get for free in UIKit
  12. Back to basics • Just use view controllers directly? •

    Can get the separation using parent/child view controllers • Hard to reuse sub-flows in navigation controllers
  13. What about deep linking? • We no longer have a

    tree of coordinators • Traverse the view controller hierarchy instead • Need to associate a given view controller with something
  14. struct DeepLink { let destination: Destination let path: [Destination] }

    enum Destination { case home case payments case spending case profile case pay case transfer case pendingPayments case accountDetail(id: String) }
  15. DeepLinkable protocol DeepLinkable { var destination: Destination { get }

    var coordinator: Coordinator { get } } final class AccountDetailViewController: UIViewController, DeepLinkable { let destination: Destination let coordinator: HomeCoordinator init(id: String, coordinator: HomeCoordinator) { self.coordinator = coordinator self.destination = .accountDetail(id: id) super.init(nibName: nil, bundle: nil) } }
  16. Tips for deep linking • Usually no need for coordinators

    • Each view controller should fetch data in isolation init(model: Account) init(id: String)