"Удобный и расширяемый роутинг в ios-приложении"...

35
Удобный и расширяемый роутинг в iOS-приложении Юсипов Тимур Avito

Upload: avitotech

Post on 15-Apr-2017

308 views

Category:

Internet


1 download

TRANSCRIPT

Page 1: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Удобный и расширяемый роутинг в iOS-приложении

Юсипов Тимур

Avito

Page 2: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Историческая справка

70187 lines of code

39051 lines of code

(2011 .. 2013) Outsource(2013 .. 2016) In-house

VIPER

Page 3: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Введем понятие Router

Как будет проходить презентация?

Рассмотрим необычные задачи Routing’а

Попробуем написать RouterАдаптируем Router под iPadСформулируем общие архитектурные правила Routing’а

Посмотрим демо

Page 4: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Что такое Module?

Router

View Controller

MVC

Model View View Interactor

Router

Presenter Entity

VIPER

RouterRouter

View Controller

MVC

Model View View InteractorPresenter Entity

VIPER

Page 5: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Что такое DeepLink?

ru.navigation.demo://categories?categoryId=182

ru.navigation.demo://authorize

ru.navigation.demo://search?categoryId=182&query=mazda

Page 6: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

presentation.start()

Page 7: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

final class ViewController: UIViewController { @objc private func onAuthorizationButtonTap(sender: UIBarButtonItem) { let authorizationController = AuthorizationViewController()

let navigationController = UINavigationController( rootViewController: authorizationController ) presentViewController(navigationController, animated: true, completion: nil) } }

Что не так с этим кодом?final class ViewController: UIViewController { @objc private func onAuthorizationButtonTap(sender: UIBarButtonItem) { let authorizationController = AuthorizationViewController()

let navigationController = UINavigationController( rootViewController: authorizationController ) presentViewController(navigationController, animated: true, completion: nil) } }

final class ViewController: UIViewController { @objc private func onAuthorizationButtonTap(sender: UIBarButtonItem) { let authorizationController = AuthorizationViewController()

let navigationController = UINavigationController( rootViewController: authorizationController ) presentViewController(navigationController, animated: true, completion: nil) } }

final class ViewController: UIViewController { @objc private func onAuthorizationButtonTap(sender: UIBarButtonItem) { let authorizationController = AuthorizationViewController()

let navigationController = UINavigationController( rootViewController: authorizationController ) presentViewController(navigationController, animated: true, completion: nil) } }

final class ViewController: UIViewController { @objc private func onAuthorizationButtonTap(sender: UIBarButtonItem) { let authorizationController = AuthorizationViewController()

let navigationController = UINavigationController( rootViewController: authorizationController ) presentViewController(navigationController, animated: true, completion: nil) } }

Добавим слой Router

Page 8: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

protocol RouterProtocol: class { func showAuthorization() }

Пробуем написать Routerprotocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: RouterProtocol { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? func showAuthorization() { let authorizationController = AuthorizationViewController() let router = AuthorizationRouterImpl() router.navigationController = navigationController router.rootViewController = authorizationController authorizationController.router = router navigationController?.pushViewController(authorizationController, animated: true) } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: RouterProtocol { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? func showAuthorization() { let authorizationController = AuthorizationViewController() let router = AuthorizationRouterImpl() router.navigationController = navigationController router.rootViewController = authorizationController authorizationController.router = router navigationController?.pushViewController(authorizationController, animated: true) } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: RouterProtocol { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? func showAuthorization() { let authorizationController = AuthorizationViewController() let router = AuthorizationRouterImpl() router.navigationController = navigationController router.rootViewController = authorizationController authorizationController.router = router navigationController?.pushViewController(authorizationController, animated: true) } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: RouterProtocol { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? func showAuthorization() { let authorizationController = AuthorizationViewController() let router = AuthorizationRouterImpl() router.navigationController = navigationController router.rootViewController = authorizationController authorizationController.router = router navigationController?.pushViewController(authorizationController, animated: true) } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: RouterProtocol { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? func showAuthorization() { let authorizationController = AuthorizationViewController() let router = AuthorizationRouterImpl() router.navigationController = navigationController router.rootViewController = authorizationController authorizationController.router = router navigationController?.pushViewController(authorizationController, animated: true) } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: RouterProtocol { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? func showAuthorization() { let authorizationController = AuthorizationViewController() let router = AuthorizationRouterImpl() router.navigationController = navigationController router.rootViewController = authorizationController authorizationController.router = router navigationController?.pushViewController(authorizationController, animated: true) } }

Добавим слой Assembly

Page 9: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Добавляем слой Assemblyprotocol AssemblyFactory: class { func authorizationAssembly() -> AuthorizationAssembly }

protocol AssemblyFactory: class { func authorizationAssembly() -> AuthorizationAssembly }

protocol AuthorizationAssembly: class { func module(navigationController: UINavigationController) -> UIViewController }

protocol AssemblyFactory: class { func authorizationAssembly() -> AuthorizationAssembly }

protocol AuthorizationAssembly: class { func module(navigationController: UINavigationController) -> UIViewController }

Page 10: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Пробуем написать Router c Assemblyprotocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: RouterProtocol { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? let assemblyFactory: AssemblyFactory

func showAuthorization() { let authorizationController = AuthorizationViewController() let router = AuthorizationRouterImpl() router.navigationController = navigationController router.rootViewController = authorizationController authorizationController.router = router navigationController?.pushViewController(authorizationController, animated: true) } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: RouterProtocol { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? let assemblyFactory: AssemblyFactory func showAuthorization() { let authorizationController = AuthorizationViewController() let router = AuthorizationRouterImpl() router.navigationController = navigationController router.rootViewController = authorizationController authorizationController.router = router navigationController?.pushViewController(authorizationController, animated: true) } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: RouterProtocol { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? let assemblyFactory: AssemblyFactory

func showAuthorization() { if let navigationController = navigationController { let authorizationAssembly = assemblyFactory.authorizationAssembly() let authorizationController = authorizationAssembly.module(navigationController) navigationController.pushViewController(authorizationController, animated: true) } } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: RouterProtocol { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? let assemblyFactory: AssemblyFactory func showAuthorization() { if let navigationController = navigationController { let authorizationAssembly = assemblyFactory.authorizationAssembly() let authorizationController = authorizationAssembly.module(navigationController) navigationController.pushViewController(authorizationController, animated: true) } } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: RouterProtocol { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? let assemblyFactory: AssemblyFactory func showAuthorization() { if let navigationController = navigationController { let authorizationAssembly = assemblyFactory.authorizationAssembly() let authorizationController = authorizationAssembly.module(navigationController) navigationController.pushViewController(authorizationController, animated: true) } } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: RouterProtocol { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? let assemblyFactory: AssemblyFactory func showAuthorization() { if let navigationController = navigationController { let authorizationAssembly = assemblyFactory.authorizationAssembly() let authorizationController = authorizationAssembly.module(navigationController) navigationController.pushViewController(authorizationController, animated: true) } } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: RouterProtocol { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? let assemblyFactory: AssemblyFactory func showAuthorization() { if let navigationController = navigationController { let authorizationAssembly = assemblyFactory.authorizationAssembly() let authorizationController = authorizationAssembly.module(navigationController) navigationController.pushViewController(authorizationController, animated: true) } } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: RouterProtocol { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? let assemblyFactory: AssemblyFactory func showAuthorization() { if let navigationController = navigationController { let authorizationAssembly = assemblyFactory.authorizationAssembly() let authorizationController = authorizationAssembly.module(navigationController) navigationController.pushViewController(authorizationController, animated: true) } } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: RouterProtocol { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? let assemblyFactory: AssemblyFactory func showAuthorization() { if let navigationController = navigationController { let authorizationAssembly = assemblyFactory.authorizationAssembly() let authorizationController = authorizationAssembly.module(navigationController) navigationController.pushViewController(authorizationController, animated: true) } } }

Добавим базовый класс

Page 11: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: RouterProtocol { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? let assemblyFactory: AssemblyFactory func showAuthorization() { if let navigationController = navigationController { let authorizationAssembly = assemblyFactory.authorizationAssembly() let authorizationController = authorizationAssembly.module(navigationController) navigationController.pushViewController(authorizationController, animated: true) } } }

Пробуем написать Router c Assembly

Добавим базовый класс

Пробуем написать Router с базовым классомprotocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: BaseRouter, RouterProtocol { func showAuthorization() { if let navigationController = navigationController { let authorizationAssembly = assemblyFactory.authorizationAssembly() let authorizationController = authorizationAssembly.module(navigationController) navigationController.pushViewController(authorizationController, animated: true) } } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: BaseRouter, RouterProtocol { func showAuthorization() { if let navigationController = navigationController { let authorizationAssembly = assemblyFactory.authorizationAssembly() let authorizationController = authorizationAssembly.module(navigationController) navigationController.pushViewController(authorizationController, animated: true) } } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: BaseRouter, RouterProtocol { func showAuthorization() { if let navigationController = navigationController { let authorizationAssembly = assemblyFactory.authorizationAssembly() let authorizationController = authorizationAssembly.module(navigationController) navigationController.pushViewController(authorizationController, animated: true) } } }

Вынесем в базовый класс

Page 12: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: BaseRouter, RouterProtocol { func showAuthorization() { if let navigationController = navigationController { let authorizationAssembly = assemblyFactory.authorizationAssembly() let authorizationController = authorizationAssembly.module(navigationController) navigationController.pushViewController(authorizationController, animated: true) } } }

Вынесем в базовый класс

Пробуем написать Router с базовым классомprotocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: BaseRouter, RouterProtocol { func showAuthorization() { pushViewControllerDerivedFrom { navigationController -> UIViewController in let authorizationAssembly = assemblyFactory.authorizationAssembly() let authorizationController = authorizationAssembly.module(navigationController)

return authorizationController } } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: BaseRouter, RouterProtocol { func showAuthorization() { pushViewControllerDerivedFrom { navigationController -> UIViewController in let authorizationAssembly = assemblyFactory.authorizationAssembly() let authorizationController = authorizationAssembly.module(navigationController)

return authorizationController } } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: BaseRouter, RouterProtocol { func showAuthorization() { pushViewControllerDerivedFrom { navigationController -> UIViewController in let authorizationAssembly = assemblyFactory.authorizationAssembly() let authorizationController = authorizationAssembly.module(navigationController)

return authorizationController } } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: BaseRouter, RouterProtocol { func showAuthorization() { pushViewControllerDerivedFrom { navigationController -> UIViewController in let authorizationAssembly = assemblyFactory.authorizationAssembly() let authorizationController = authorizationAssembly.module(navigationController)

return authorizationController } } }

protocol RouterProtocol: class { func showAuthorization() }

final class RouterProtocolImpl: BaseRouter, RouterProtocol { func showAuthorization() { presentModalViewControllerDerivedFrom { navigationController -> UIViewController in let authorizationAssembly = assemblyFactory.authorizationAssembly() let authorizationController = authorizationAssembly.module(navigationController)

return authorizationController } } }

Хороший фасад

Page 13: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Базовый классclass BaseRouter { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? func pushViewControllerDerivedFrom(deriveViewController: UINavigationController -> UIViewController) { if let navigationController = navigationController { let viewController = deriveViewController(navigationController) navigationController.pushViewController(viewController, animated: true) } } }

class BaseRouter { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? func pushViewControllerDerivedFrom(deriveViewController: UINavigationController -> UIViewController) { if let navigationController = navigationController { let viewController = deriveViewController(navigationController) navigationController.pushViewController(viewController, animated: true) } } }

Что делать с Master-detail модулем?

Для Master-detail нужен второй навигационный контроллер

Page 14: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Второй базовый классclass BaseMasterDetailRouter { weak var masterNavigationController: UINavigationController? weak var detailNavigationController: UINavigationController? weak var rootViewController: UIViewController? func pushMasterViewControllerDerivedFrom(deriveViewController: UINavigationController -> UIViewController) { if let navigationController = masterNavigationController { let viewController = deriveViewController(navigationController) navigationController.pushViewController(viewController, animated: true) } } }

class BaseMasterDetailRouter { weak var masterNavigationController: UINavigationController? weak var detailNavigationController: UINavigationController? weak var rootViewController: UIViewController? func pushMasterViewControllerDerivedFrom(deriveViewController: UINavigationController -> UIViewController) { if let navigationController = masterNavigationController { let viewController = deriveViewController(navigationController) navigationController.pushViewController(viewController, animated: true) } } }

class BaseMasterDetailRouter { weak var masterNavigationController: UINavigationController? weak var detailNavigationController: UINavigationController? weak var rootViewController: UIViewController? func pushMasterViewControllerDerivedFrom(deriveViewController: UINavigationController -> UIViewController) { if let navigationController = masterNavigationController { let viewController = deriveViewController(navigationController) navigationController.pushViewController(viewController, animated: true) } } }

class BaseMasterDetailRouter { weak var masterNavigationController: UINavigationController? weak var detailNavigationController: UINavigationController? weak var rootViewController: UIViewController? func pushMasterViewControllerDerivedFrom(deriveViewController: UINavigationController -> UIViewController) { if let navigationController = masterNavigationController { let viewController = deriveViewController(navigationController) navigationController.pushViewController(viewController, animated: true) } } }

class BaseMasterDetailRouter { weak var masterNavigationController: UINavigationController? weak var detailNavigationController: UINavigationController? weak var rootViewController: UIViewController? func pushMasterViewControllerDerivedFrom(deriveViewController: UINavigationController -> UIViewController) { if let navigationController = masterNavigationController { let viewController = deriveViewController(navigationController) navigationController.pushViewController(viewController, animated: true) } } }

class BaseMasterDetailRouter { weak var masterNavigationController: UINavigationController? weak var detailNavigationController: UINavigationController? weak var rootViewController: UIViewController? func pushMasterViewControllerDerivedFrom(deriveViewController: UINavigationController -> UIViewController) { if let navigationController = masterNavigationController { let viewController = deriveViewController(navigationController) navigationController.pushViewController(viewController, animated: true) } } }

Добавим структурку для передачи

всех нужных роутерупараметров

Page 15: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Добавляем структуркиstruct RouterSeed { let navigationController: UINavigationController }

struct RouterSeed { let navigationController: UINavigationController }

struct MasterDetailRouterSeed { let masterNavigationController: UINavigationController let detailNavigationController: UINavigationController }

Теперь рефакторитьбудет удобней

Page 16: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Улучшенный фасад pushViewControllerDerivedFrom { routerSeed -> UIViewController in pushViewControllerDerivedFrom { routerSeed -> UIViewController in

pushMasterViewControllerDerivedFrom { routerSeed -> UIViewController in

pushViewControllerDerivedFrom { routerSeed -> UIViewController in

pushMasterViewControllerDerivedFrom { routerSeed -> UIViewController in

setDetailViewControllerDerivedFrom { routerSeed -> UIViewController in

pushViewControllerDerivedFrom { routerSeed -> UIViewController in

pushMasterViewControllerDerivedFrom { routerSeed -> UIViewController in

setDetailViewControllerDerivedFrom { routerSeed -> UIViewController in

presentModalNavigationControllerWithRootViewControllerDerivedFrom { routerSeed -> UIViewController in

pushViewControllerDerivedFrom { routerSeed -> UIViewController in

pushMasterViewControllerDerivedFrom { routerSeed -> UIViewController in

setDetailViewControllerDerivedFrom { routerSeed -> UIViewController in

presentModalNavigationControllerWithRootViewControllerDerivedFrom { routerSeed -> UIViewController in

presentPopoverWithNavigationControllerFromBarButtonItem(buttonItem) { routerSeed -> UIViewController in

Page 17: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

So far, so goodМодуль авторизации из всех модулейПоддержка DeepLinksBonus: (Push’ы, Alert’ы)

Нужно научить базовые роутеры

искать верхний модуль

So far, so good, но что если

Page 18: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Поиск верхнего модуляprotocol TopViewControllerFinder: class { func topViewController() -> UIViewController? }

final class TopViewControllerFinderImpl: TopViewControllerFinder { weak var rootViewController: UIViewController? }

final class TopViewControllerFinderImpl: TopViewControllerFinder { weak var rootViewController: UIViewController? func topViewController() -> UIViewController? { var result = rootViewController while let presentedViewController = result?.presentedViewController { result = presentedViewController } return result } }

final class TopViewControllerFinderImpl: TopViewControllerFinder { weak var rootViewController: UIViewController? func topViewController() -> UIViewController? { var result = rootViewController while let presentedViewController = result?.presentedViewController { result = presentedViewController } return result } }

final class TopViewControllerFinderImpl: TopViewControllerFinder { weak var rootViewController: UIViewController? func topViewController() -> UIViewController? { var result = rootViewController while let presentedViewController = result?.presentedViewController { result = presentedViewController } if let selectedTabController = (result as? UITabBarController)?.selectedViewController { if let detailController = (selectedTabController as? UISplitViewController)?.viewControllers.last { if let detailNavigationController = detailController as? UINavigationController { result = detailNavigationController.viewControllers.last } else { result = detailController } } else { result = selectedTabController } } return result } }

final class TopViewControllerFinderImpl: TopViewControllerFinder { weak var rootViewController: UIViewController? func topViewController() -> UIViewController? { var result = rootViewController while let presentedViewController = result?.presentedViewController { result = presentedViewController } if let selectedTabController = (result as? UITabBarController)?.selectedViewController { if let detailController = (selectedTabController as? UISplitViewController)?.viewControllers.last { if let detailNavigationController = detailController as? UINavigationController { result = detailNavigationController.viewControllers.last } else { result = detailController } } else { result = selectedTabController } } return result } }

Нужна своя система навигации

Page 19: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Зачем нужна своя система навигации?Хранение истории переходов

Поддержка Third-party контроллеров

Bonus: проверка, что модуль был на экранеBonus: расстояние между модулями

Поиск верхнего модуля

Обертка над UIKit

Реакция на изменение SDK

Page 20: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Свежий взгляд на базовый Routerclass BaseRouter { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? }

class BaseRouter { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? }

Нужно абстрагировать Router от UIKit

Не у каждого роутера будет UINavigationController

Код вида .pushViewController() сильно завязывает Router на UIKit

Для каждого ThirdPartyNavigationController нужна будет своя пара базовых Router’ов

Page 21: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Абстрагируем Router от UIKit

protocol TransitionsHandler: class { }

typealias TransitionId = String Идентификатор перехода

Возвращение на модуль

Закрытие модуля

Отменяемый переходprotocol TransitionsHandler: class { func performTransition(context context: PresentationTransitionContext) }

Неотменяемый переход

protocol TransitionsHandler: class { func performTransition(context context: PresentationTransitionContext)

func resetWithTransition(context context: ResettingTransitionContext) }

protocol TransitionsHandler: class { func performTransition(context context: PresentationTransitionContext)

func resetWithTransition(context context: ResettingTransitionContext)

func undoTransitionsAfter(transitionId transitionId: TransitionId) }

protocol TransitionsHandler: class { func performTransition(context context: PresentationTransitionContext)

func resetWithTransition(context context: ResettingTransitionContext)

func undoTransitionsAfter(transitionId transitionId: TransitionId)

func undoTransitionWith(transitionId transitionId: TransitionId) }

Router общается с

обработчиком переходов

Page 22: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Обработчик переходов оборачивает UIViewController

Виды модулей Анимирующие

AnimatingTransitionsHandlerImpl

NavigationTransitionsHandlerImpl

ContainerTransitionsHandlerImpl

SplitViewTransitionsHandlerImpl

TabBarTransitionsHandlerImpl

Контейнеры

pushViewController(_:animated:)presentViewController(_:animated:completion:)

visibleAnimatingTransitionsHandlers()allAnimatingTransitionsHandlers()

Легко добавить Third-party контроллер

Page 23: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

class BaseRouter { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? }

Новый базовый Routerclass BaseRouter { let transitionsHandlerBox: TransitionsHandlerBox // weak var navigationController: UINavigationController? let transitionId: TransitionId // weak var rootViewController: UIViewController? }

class BaseRouter { let transitionsHandlerBox: TransitionsHandlerBox // weak var navigationController: UINavigationController? let transitionId: TransitionId // weak var rootViewController: UIViewController? }

class BaseRouter { let transitionsHandlerBox: TransitionsHandlerBox // weak var navigationController: UINavigationController? let transitionId: TransitionId // weak var rootViewController: UIViewController? }

class BaseRouter { let transitionsHandlerBox: TransitionsHandlerBox // weak var navigationController: UINavigationController? let transitionId: TransitionId // weak var rootViewController: UIViewController? }

enum TransitionsHandlerBox { case Animating(AnimatingTransitionsHandlerImpl) case Containing(ContainingTransitionsHandlerImpl) }

Такой Router можно использовать

с любым UIViewController’ом

Page 24: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Схема выполнения отменяемых переходов

Transitions handlerbox

выполни отменяемый переход

Router

presentation context

transitions handler

box Transitions Coordinator

Top animating transitions handler запусти

анимацию

presentation context

Page 25: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Взглянем еще раз на новый базовый Routerclass BaseRouter { let transitionsHandlerBox: TransitionsHandlerBox // weak var navigationController: UINavigationController? let transitionId: TransitionId // weak var rootViewController: UIViewController? }

Нужна ссылка на обработчика переходов, показавшего модуль Router’а

class BaseRouter { let transitionsHandlerBox: TransitionsHandlerBox // weak var navigationController: UINavigationController? let transitionId: TransitionId // weak var rootViewController: UIViewController? weak var presentingTransitionsHandler: TransitionsHandler? }

class BaseRouter { let transitionsHandlerBox: TransitionsHandlerBox // weak var navigationController: UINavigationController? let transitionId: TransitionId // weak var rootViewController: UIViewController? weak var presentingTransitionsHandler: TransitionsHandler? }

Теперь роутер может закрывать свой модуль

Page 26: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Навигационная связь

Router 1

transition id 1

Transitions handler 1

Transitions handler 2

presenting transitions

handler

Вернись на модуль 1

Закрой модуль 2

Router 2

transition id 2

Что лучше: “Вернись на модуль 1”

или “Закрой модуль 2”

?

Page 27: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Flow

Фильтр Города

Page 28: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Выход из Flow

Фильтр Города

Router

dismiss cities

Page 29: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Усложненный flow

Фильтр Регионы Города

Page 30: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Выход из Flow

Фильтрmodule output

ГородаРегионыmodule output

Router

return to filter

“Вернись на модуль” гибче, чем

“Закрой модуль”

Page 31: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Слой Router

Предварительные итоги

Подходы к выполнению обратных переходовПоддержка DeepLinks

Слой AssemblyБазовые классы Router, поддержка iPad, master-detailПростой Router (фасад, абстрация от UIKit, поддержка Third-Party)

Page 32: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

demo.start()

Page 33: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Один UIViewController, много Router’ов

Выводы по демо

Проверка наличия модуля в истории (Авторизация)

Проверка isIpad()Поиск верхнего модуля (Авторизация, DeepLink’и, Push’ы)

Проверка модулей на дубликаты (🍌, 🍏)Аниматоры переходов

Проверка isIpad()

Page 34: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

Выделите слой Router (определять стиль перехода)

Общие советы

Используйте “Вернись на модуль” вместо “Закрой модуль”

Выделите слой Assembly (верьте в появление DI для Swift)Абстрагируйте Router от UIKit

Вынесите логику принятия решений в отдельный слойОписывайте переходы в декларативном стиле

Page 35: "Удобный и расширяемый роутинг в iOS-приложении" Тимур  Юсипов (Avito)

One more thing

https://github.com/avito-tech/Marshroute

Исходники Докладчик: Юсипов Тимурhttps://vk.com/ma3tsa

[email protected]

[email protected]

fizmatchelskype

personal mail

work mail

vk

Marshroute

Спасибо за внимание!

presentation.finish()

https://github.com/avito-tech/Marshroute/tree/master/Example

Демо