SideMenuPushCoordinator.swift 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. //
  2. // PushCoordinator.swift
  3. // SideMenu
  4. //
  5. // Created by Jon Kent on 9/4/19.
  6. //
  7. import UIKit
  8. protocol CoordinatorModel {
  9. var animated: Bool { get }
  10. var fromViewController: UIViewController { get }
  11. var toViewController: UIViewController { get }
  12. }
  13. protocol Coordinator {
  14. associatedtype Model: CoordinatorModel
  15. init(config: Model)
  16. @discardableResult func start() -> Bool
  17. }
  18. internal final class SideMenuPushCoordinator: Coordinator {
  19. struct Model: CoordinatorModel {
  20. var allowPushOfSameClassTwice: Bool
  21. var alongsideTransition: (() -> Void)?
  22. var animated: Bool
  23. var fromViewController: UIViewController
  24. var pushStyle: SideMenuPushStyle
  25. var toViewController: UIViewController
  26. }
  27. private let config: Model
  28. init(config: Model) {
  29. self.config = config
  30. }
  31. @discardableResult func start() -> Bool {
  32. guard config.pushStyle != .subMenu,
  33. let fromNavigationController = config.fromViewController as? UINavigationController else {
  34. return false
  35. }
  36. let toViewController = config.toViewController
  37. let presentingViewController = fromNavigationController.presentingViewController
  38. let splitViewController = presentingViewController as? UISplitViewController
  39. let tabBarController = presentingViewController as? UITabBarController
  40. let potentialNavigationController = (splitViewController?.viewControllers.first ?? tabBarController?.selectedViewController) ?? presentingViewController
  41. guard let navigationController = potentialNavigationController as? UINavigationController else {
  42. Print.warning(.cannotPush, arguments: String(describing: potentialNavigationController.self), required: true)
  43. return false
  44. }
  45. // To avoid overlapping dismiss & pop/push calls, create a transaction block where the menu
  46. // is dismissed after showing the appropriate screen
  47. CATransaction.begin()
  48. defer { CATransaction.commit() }
  49. UIView.animationsEnabled { [weak self] in
  50. self?.config.alongsideTransition?()
  51. }
  52. if let lastViewController = navigationController.viewControllers.last,
  53. !config.allowPushOfSameClassTwice && type(of: lastViewController) == type(of: toViewController) {
  54. return false
  55. }
  56. toViewController.navigationItem.hidesBackButton = config.pushStyle.hidesBackButton
  57. switch config.pushStyle {
  58. case .default:
  59. navigationController.pushViewController(toViewController, animated: config.animated)
  60. return true
  61. // subMenu handled earlier
  62. case .subMenu:
  63. return false
  64. case .popWhenPossible:
  65. for subViewController in navigationController.viewControllers.reversed() {
  66. if type(of: subViewController) == type(of: toViewController) {
  67. navigationController.popToViewController(subViewController, animated: config.animated)
  68. return true
  69. }
  70. }
  71. navigationController.pushViewController(toViewController, animated: config.animated)
  72. return true
  73. case .preserve, .preserveAndHideBackButton:
  74. var viewControllers = navigationController.viewControllers
  75. let filtered = viewControllers.filter { preservedViewController in type(of: preservedViewController) == type(of: toViewController) }
  76. guard let preservedViewController = filtered.last else {
  77. navigationController.pushViewController(toViewController, animated: config.animated)
  78. return true
  79. }
  80. viewControllers = viewControllers.filter { subViewController in subViewController !== preservedViewController }
  81. viewControllers.append(preservedViewController)
  82. navigationController.setViewControllers(viewControllers, animated: config.animated)
  83. return true
  84. case .replace:
  85. navigationController.setViewControllers([toViewController], animated: config.animated)
  86. return true
  87. }
  88. }
  89. }