Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Удобный и расширяемый
роутинг в iOS-приложении
Юсипов Тимур
Avito
Историческая справка
70187
lines of code
39051
lines of code
(2011 .. 2013) Outsource
(2013 .. 2016) In-house
VIPER
Введем понятие Router
Как будет проходить презентация?
Рассмотрим необычные задачи Routing’а
Попробуем написать Router
Ада...
Что такое Module?
Router
View
Controller
MVC
Model View View Interactor
Router
Presenter Entity
VIPER
RouterRouter
View
Co...
Что такое DeepLink?
ru.navigation.demo://categories?categoryId=182
ru.navigation.demo://authorize
ru.navigation.demo://sea...
presentation.start()
final class ViewController: UIViewController {
@objc private func onAuthorizationButtonTap(sender: UIBarButtonItem) {
let ...
protocol RouterProtocol: class {
func showAuthorization()
}
Пробуем написать Router
protocol RouterProtocol: class {
func ...
Добавляем слой Assembly
protocol AssemblyFactory: class {
func authorizationAssembly() -> AuthorizationAssembly
}
protocol...
Пробуем написать Router c Assembly
protocol RouterProtocol: class {
func showAuthorization()
}
final class RouterProtocolI...
protocol RouterProtocol: class {
func showAuthorization()
}
final class RouterProtocolImpl: RouterProtocol {
weak var navi...
protocol RouterProtocol: class {
func showAuthorization()
}
final class RouterProtocolImpl: BaseRouter, RouterProtocol {
f...
Базовый класс
class BaseRouter {
weak var navigationController: UINavigationController?
weak var rootViewController: UIVie...
Второй базовый класс
class BaseMasterDetailRouter {
weak var masterNavigationController: UINavigationController?
weak var ...
Добавляем структурки
struct RouterSeed {
let navigationController: UINavigationController
}
struct RouterSeed {
let naviga...
Улучшенный фасад
pushViewControllerDerivedFrom { routerSeed -> UIViewController inpushViewControllerDerivedFrom { routerSe...
So far, so good
Модуль авторизации из всех модулей
Поддержка DeepLinks
Bonus: (Push’ы, Alert’ы)
Нужно научить
базовые роут...
Поиск верхнего модуля
protocol TopViewControllerFinder: class {
func topViewController() -> UIViewController?
}
final clas...
Зачем нужна своя система навигации?
Хранение истории переходов
Поддержка Third-party контроллеров
Bonus: проверка, что мод...
Свежий взгляд на базовый Router
class BaseRouter {
weak var navigationController: UINavigationController?
weak var rootVie...
Абстрагируем Router от UIKit
protocol TransitionsHandler: class {
}
typealias TransitionId = String Идентификатор
перехода...
Обработчик переходов оборачивает UIViewController
Виды модулей
Анимирующие
AnimatingTransitionsHandlerImpl
NavigationTrans...
class BaseRouter {
weak var navigationController: UINavigationController?
weak var rootViewController: UIViewController?
}...
Схема выполнения отменяемых переходов
Transitions handler
box
выполни
отменяемый
переход
Router
presentation
context
trans...
Взглянем еще раз на новый базовый Router
class BaseRouter {
let transitionsHandlerBox: TransitionsHandlerBox // weak var n...
Навигационная связь
Router
1
transition id
1
Transitions handler
1
Transitions handler
2
presenting
transitions
handler
Ве...
Flow
Фильтр Города
Выход из Flow
Фильтр Города
Router
dismiss
cities
Усложненный flow
Фильтр Регионы Города
Выход из Flow
Фильтр
module
output
ГородаРегионы
module
output
Router
return
to filter
“Вернись на модуль”
гибче, чем
“Зак...
Слой Router
Предварительные итоги
Подходы к выполнению обратных переходов
Поддержка DeepLinks
Слой Assembly
Базовые классы...
demo.start()
Один UIViewController, много Router’ов
Выводы по демо
Проверка наличия модуля в истории (Авторизация)
Проверка isIpad()
По...
Выделите слой Router (определять стиль перехода)
Общие советы
Используйте “Вернись на модуль” вместо “Закрой модуль”
Выдел...
One more thing
https://github.com/avito-tech/Marshroute
Исходники Докладчик: Юсипов Тимур
https://vk.com/ma3tsa
tyusipov@a...
Upcoming SlideShare
Loading in …5
×

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

452 views

Published on

В своём докладе я расскажу о подходе к построению навигации в больших приложениях на примере демо-проекта, приближенного по архитектуре к приложению Avito.

Данная архитектура позволяет поддерживать DeepLink’и и iPad в существующем приложении, показывать плашки Push-уведомлений в верхнем видимом модуле, совершать переходы из верхнего видимого модуля, вызванные корневым модулем приложения, а также централизовано управлять анимацией переходов.

Разработанная нами навигация оборачивает UIKit для совершения переходов в засахаренном и декларативном стиле. Использование этой системы позволило снизить связанность кода для повышения его переиспользуемости и тестируемости, а также выработать общие стандарты к построению стека навигации. Наиболее хорошо эта система сочетается с архитектурой VIPER, но может использоваться и без нее.

По ходу доклада я также расскажу о том, как выглядела навигация в приложении Avito раньше; о том, какой она стала с началом использования архитектуры VIPER; о проблемах, которые мы увидели, и об итоговом решении. Также вкратце рассмотрим, что же скрывается под капотом у этой системы.

Published in: Engineering
  • Be the first to comment

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

  1. 1. Удобный и расширяемый роутинг в iOS-приложении Юсипов Тимур Avito
  2. 2. Историческая справка 70187 lines of code 39051 lines of code (2011 .. 2013) Outsource (2013 .. 2016) In-house VIPER
  3. 3. Введем понятие Router Как будет проходить презентация? Рассмотрим необычные задачи Routing’а Попробуем написать Router Адаптируем Router под iPad Сформулируем общие архитектурные правила Routing’а Посмотрим демо
  4. 4. Что такое Module? Router View Controller MVC Model View View Interactor Router Presenter Entity VIPER RouterRouter View Controller MVC Model View View InteractorPresenter Entity VIPER
  5. 5. Что такое DeepLink? ru.navigation.demo://categories?categoryId=182 ru.navigation.demo://authorize ru.navigation.demo://search?categoryId=182&query=mazda
  6. 6. presentation.start()
  7. 7. 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
  8. 8. protocol RouterProtocol: class { func showAuthorization() } Пробуем написать Router 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) } } 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
  9. 9. Добавляем слой Assembly protocol 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 }
  10. 10. Пробуем написать Router c Assembly 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() { 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) } } } Добавим базовый класс
  11. 11. 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) } } } Вынесем в базовый класс
  12. 12. 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 } } } Хороший фасад
  13. 13. Базовый класс 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 нужен второй навигационный контроллер
  14. 14. Второй базовый класс 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) } } } Добавим структурку для передачи всех нужных роутеру параметров
  15. 15. Добавляем структурки struct RouterSeed { let navigationController: UINavigationController } struct RouterSeed { let navigationController: UINavigationController } struct MasterDetailRouterSeed { let masterNavigationController: UINavigationController let detailNavigationController: UINavigationController } Теперь рефакторить будет удобней
  16. 16. Улучшенный фасад pushViewControllerDerivedFrom { routerSeed -> UIViewController inpushViewControllerDerivedFrom { 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
  17. 17. So far, so good Модуль авторизации из всех модулей Поддержка DeepLinks Bonus: (Push’ы, Alert’ы) Нужно научить базовые роутеры искать верхний модуль So far, so good, но что если
  18. 18. Поиск верхнего модуля 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 } } Нужна своя система навигации
  19. 19. Зачем нужна своя система навигации? Хранение истории переходов Поддержка Third-party контроллеров Bonus: проверка, что модуль был на экране Bonus: расстояние между модулями Поиск верхнего модуля Обертка над UIKit Реакция на изменение SDK
  20. 20. Свежий взгляд на базовый Router class 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’ов
  21. 21. Абстрагируем 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 общается с обработчиком переходов
  22. 22. Обработчик переходов оборачивает UIViewController Виды модулей Анимирующие AnimatingTransitionsHandlerImpl NavigationTransitionsHandlerImpl ContainerTransitionsHandlerImpl SplitViewTransitionsHandlerImpl TabBarTransitionsHandlerImpl Контейнеры pushViewController(_:animated:) presentViewController(_:animated:completion:) visibleAnimatingTransitionsHandlers() allAnimatingTransitionsHandlers() Легко добавить Third-party контроллер
  23. 23. class BaseRouter { weak var navigationController: UINavigationController? weak var rootViewController: UIViewController? } Новый базовый Router 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? } 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’ом
  24. 24. Схема выполнения отменяемых переходов Transitions handler box выполни отменяемый переход Router presentation context transitions handler box Transitions Coordinator Top animating transitions handlerзапусти анимацию presentation context
  25. 25. Взглянем еще раз на новый базовый Router class 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? } Теперь роутер может закрывать свой модуль
  26. 26. Навигационная связь Router 1 transition id 1 Transitions handler 1 Transitions handler 2 presenting transitions handler Вернись на модуль 1 Закрой модуль 2 Router 2 transition id 2 Что лучше: “Вернись на модуль 1” или “Закрой модуль 2” ?
  27. 27. Flow Фильтр Города
  28. 28. Выход из Flow Фильтр Города Router dismiss cities
  29. 29. Усложненный flow Фильтр Регионы Города
  30. 30. Выход из Flow Фильтр module output ГородаРегионы module output Router return to filter “Вернись на модуль” гибче, чем “Закрой модуль”
  31. 31. Слой Router Предварительные итоги Подходы к выполнению обратных переходов Поддержка DeepLinks Слой Assembly Базовые классы Router, поддержка iPad, master-detail Простой Router (фасад, абстрация от UIKit, поддержка Third-Party)
  32. 32. demo.start()
  33. 33. Один UIViewController, много Router’ов Выводы по демо Проверка наличия модуля в истории (Авторизация) Проверка isIpad() Поиск верхнего модуля (Авторизация, DeepLink’и, Push’ы) Проверка модулей на дубликаты (🍌, 🍏) Аниматоры переходов Проверка isIpad()
  34. 34. Выделите слой Router (определять стиль перехода) Общие советы Используйте “Вернись на модуль” вместо “Закрой модуль” Выделите слой Assembly (верьте в появление DI для Swift) Абстрагируйте Router от UIKit Вынесите логику принятия решений в отдельный слой Описывайте переходы в декларативном стиле
  35. 35. One more thing https://github.com/avito-tech/Marshroute Исходники Докладчик: Юсипов Тимур https://vk.com/ma3tsa tyusipov@avito.ru ykylele@gmail.com fizmatchelskype personal mail work mail vk Marshroute Спасибо за внимание! presentation.finish() https://github.com/avito-tech/Marshroute/tree/master/Example Демо

×