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

Coordinators and deep linking

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers. →
rechsteiner
February 13, 2020

Coordinators and deep linking

How we handle navigation inside the mobile bank at DNB

rechsteiner

February 13, 2020

Transcript

29 slides
  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)

More Decks by rechsteiner

Other Decks in Programming