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.

ApplicationCoordinator для навигации между экранами / Павел Гуров (Avito)

159 views

Published on

РИТ++ 2017, AppsConf
Зал Найроби + Касабланка, 5 июня, 12:00

Тезисы:
http://appsconf.ru/2017/abstracts/2711.html

Навигация между экранами - задача, которая появляется в приложении когда экранов становится больше чем один, то-есть сразу. Стандартные подходы к её решению в iOS (segues, present(_:animated), UINavigationController) обычно используются внутри кода экранов, что приводит к их жесткой привязанности друг к другу и к сценарию, в котором они участвуют.

Доклад о том, как вынести решение этой задачи из Presentation-слоя с использованием паттерна Application Coordinator. Основан на опыте построения навигации между экранами в профессиональных приложениях Avito. Будет интересен тем, кто ищет способ сделать экраны независимыми, переиспользуемыми и легко трестируемыми.

Published in: Engineering
  • Be the first to comment

  • Be the first to like this

ApplicationCoordinator для навигации между экранами / Павел Гуров (Avito)

  1. 1. Павел Гуров pegurov@avito.ru Application Coordinator в iOS приложениях
  2. 2. 2 +
  3. 3. Application Coordinator 3
  4. 4. Application Coordinator 3 • Проблемы и их решение
  5. 5. Application Coordinator 3 • Проблемы и их решение • Демо-проект
  6. 6. Application Coordinator 3 • Проблемы и их решение • Демо-проект • Внедрение в существующий код
  7. 7. 4
  8. 8. MVx 5
  9. 9. 6 UIStoryboard + segues
  10. 10. 6 UIStoryboard + segues Containers
  11. 11. 6 UIStoryboard + segues Containers present(_:animated:completion:)
  12. 12. 7 UIStoryboard + segues Containers present(_:animated:completion:)
  13. 13. 7 self.performSegue(withIdentifier: "", sender: self) self.navigationController self.present(next, animated: true, completion: nil) UIStoryboard + segues Containers present(_:animated:completion:)
  14. 14. 8 /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare( for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using // segue.destinationViewController. // Pass the selected object to the // new view controller. } */
  15. 15. 8 /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare( for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using // segue.destinationViewController. // Pass the selected object to the // new view controller. } */ /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare( for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using // segue.destinationViewController. // Pass the selected object to the // new view controller. } */
  16. 16. 8 /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare( for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using // segue.destinationViewController. // Pass the selected object to the // new view controller. } */ /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare( for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using // segue.destinationViewController. // Pass the selected object to the // new view controller. } */
  17. 17. Жесткая связанность 9 1 2
  18. 18. Жесткая связанность 9 1 2
  19. 19. Жесткая связанность 9 1 2 3
  20. 20. Жесткая связанность 9 1 2 3
  21. 21. Порядок 10 1 2 3
  22. 22. Порядок 10 123
  23. 23. Передача данных 11 1 2 3
  24. 24. Передача данных 11 1 2 3
  25. 25. Передача данных 11 1 2 3
  26. 26. Выбор города 12
  27. 27. Выбор города 12
  28. 28. Выбор города 12
  29. 29. Выбор города 12
  30. 30. Выбор города 12
  31. 31. Выбор города 12
  32. 32. 13
  33. 33. 13
  34. 34. 13 self.navigationController self.tabBarController
  35. 35. 13 performSegue( withIdentifier: "next", sender: self ) prepare( for segue: UIStoryboardSegue, sender: Any? )
  36. 36. 13 let next = CitiesViewController() next.city = City(name: "Moscow")
  37. 37. 13
  38. 38. 14 Coordinator
  39. 39. 14 Coordinator
  40. 40. 14 Coordinator
  41. 41. 14 Coordinator
  42. 42. 14 Coordinator
  43. 43. 15 Coordinator
  44. 44. 15 Coordinator Presentation Logic Data Database Storage
  45. 45. Demo 16
  46. 46. Inputs and outputs 17 class UserEditViewController: UIViewController { // MARK: - Input - var user: User? { didSet { updateView() } } // MARK: - Output - var onSelectCity: (() -> Void)? } class CitiesViewController: UITableViewController { // MARK: - Output - var onCitySelected: ((City) -> Void)? }
  47. 47. Inputs and outputs 17 class UserEditViewController: UIViewController { // MARK: - Input - var user: User? { didSet { updateView() } } // MARK: - Output - var onSelectCity: (() -> Void)? } class CitiesViewController: UITableViewController { // MARK: - Output - var onCitySelected: ((City) -> Void)? }
  48. 48. Inputs and outputs 17 class UserEditViewController: UIViewController { // MARK: - Input - var user: User? { didSet { updateView() } } // MARK: - Output - var onSelectCity: (() -> Void)? } class CitiesViewController: UITableViewController { // MARK: - Output - var onCitySelected: ((City) -> Void)? }
  49. 49. 18
  50. 50. 18 “interface” CitiesOutput + onCitySelected: (City)->() protocol CitiesOutput { var onCitySelected: ((City) -> Void)? { get set } }
  51. 51. 18 “interface” CitiesOutput + onCitySelected: (City)->() CitiesViewController protocol CitiesOutput { var onCitySelected: ((City) -> Void)? { get set } } class CitiesViewController: CitiesOutput { var onCitySelected: ((City) -> Void)? }
  52. 52. 18 “interface” CitiesOutput + onCitySelected: (City)->() CitiesViewController UserEditCoordinator + start() + controllerFactory: ControllerFactory protocol CitiesOutput { var onCitySelected: ((City) -> Void)? { get set } } class CitiesViewController: CitiesOutput { var onCitySelected: ((City) -> Void)? }
  53. 53. 18 “interface” CitiesOutput + onCitySelected: (City)->() CitiesViewController UserEditCoordinator + start() + controllerFactory: ControllerFactory ControllerFactory + cityOutput(): CitiesOutput protocol CitiesOutput { var onCitySelected: ((City) -> Void)? { get set } } class CitiesViewController: CitiesOutput { var onCitySelected: ((City) -> Void)? }
  54. 54. 18 “interface” CitiesOutput + onCitySelected: (City)->() CitiesViewController UserEditCoordinator + start() + controllerFactory: ControllerFactory ControllerFactory + cityOutput(): CitiesOutput protocol CitiesOutput { var onCitySelected: ((City) -> Void)? { get set } } class CitiesViewController: CitiesOutput { var onCitySelected: ((City) -> Void)? }
  55. 55. Composition VS Inheritance 19
  56. 56. Composition VS Inheritance 19 Coordinator UINavigationController
  57. 57. Composition VS Inheritance 19 Coordinator UINavigationController Coordinator: UINavigationController
  58. 58. Storyboards 20
  59. 59. Storyboards 21 class BaseViewController: UIViewController { var onPrepareForSegue: ((UIStoryboardSegue, Any?) -> ())? override func prepare(for segue: UIStoryboardSegue, sender: Any?) { onPrepareForSegue?(segue, sender) } }
  60. 60. Storyboards 21 class BaseViewController: UIViewController { var onPrepareForSegue: ((UIStoryboardSegue, Any?) -> ())? override func prepare(for segue: UIStoryboardSegue, sender: Any?) { onPrepareForSegue?(segue, sender) } }
  61. 61. 22 class StoryboardCoordinator<RootType: UIViewController> { let navigationController: UINavigationController let rootViewController: RootType init(storyboard: UIStoryboard) { navigationController = storyboard.initialNavigation rootViewController = navigationController!.topViewController as! RootType } } typealias UserEditCoordinator = StoryboardCoordinator<UserEditViewController>
  62. 62. 22 class StoryboardCoordinator<RootType: UIViewController> { let navigationController: UINavigationController let rootViewController: RootType init(storyboard: UIStoryboard) { navigationController = storyboard.initialNavigation rootViewController = navigationController!.topViewController as! RootType } } typealias UserEditCoordinator = StoryboardCoordinator<UserEditViewController>
  63. 63. 22 class StoryboardCoordinator<RootType: UIViewController> { let navigationController: UINavigationController let rootViewController: RootType init(storyboard: UIStoryboard) { navigationController = storyboard.initialNavigation rootViewController = navigationController!.topViewController as! RootType } } typealias UserEditCoordinator = StoryboardCoordinator<UserEditViewController>
  64. 64. 22 class StoryboardCoordinator<RootType: UIViewController> { let navigationController: UINavigationController let rootViewController: RootType init(storyboard: UIStoryboard) { navigationController = storyboard.initialNavigation rootViewController = navigationController!.topViewController as! RootType } } typealias UserEditCoordinator = StoryboardCoordinator<UserEditViewController>
  65. 65. 23 Coordinator
  66. 66. 23 Coordinator
  67. 67. 23 Coordinator
  68. 68. 23 Coordinator
  69. 69. 23 Coordinator
  70. 70. Рефакторинг существующего кода 24
  71. 71. 25
  72. 72. 25 LoginCoordinator FeedCoordinator UserEditCoordinator
  73. 73. 26 LoginCoordinator FeedCoordinator UserEditCoordinator
  74. 74. 26 LoginCoordinator FeedCoordinator UserEditCoordinator
  75. 75. 26 ApplicationCoordinator LoginCoordinator FeedCoordinator UserEditCoordinator AppDelegate
  76. 76. 26 ApplicationCoordinator LoginCoordinator FeedCoordinator UserEditCoordinator AppDelegate
  77. 77. 26 ApplicationCoordinator LoginCoordinator FeedCoordinator UserEditCoordinator AppDelegate
  78. 78. 26 ApplicationCoordinator LoginCoordinator FeedCoordinator UserEditCoordinator AppDelegate
  79. 79. 27 class ViewController: UIViewController { var nextCoordinator: NextCoordinator? @IBAction func someAction(_ sender: UIButton) { nextCoordinator = NextCoordinator( navVC: navigationController ) nextCoordinator?.start() } } Временный старт Coordinator внутри UIViewController
  80. 80. Итог 28
  81. 81. Итог • Независимые экраны и сценарии 28
  82. 82. Итог • Независимые экраны и сценарии • Легко менять порядок 28
  83. 83. Итог • Независимые экраны и сценарии • Легко менять порядок • Упрощается передача данных между экранами 28
  84. 84. Итог • Независимые экраны и сценарии • Легко менять порядок • Упрощается передача данных между экранами • Никаких сторонних зависимостей =) 28
  85. 85. Итог • Независимые экраны и сценарии • Легко менять порядок • Упрощается передача данных между экранами • Никаких сторонних зависимостей =) 28 github.com/pegurov/CoordinatorsDemo pegurov@avito.ru

×