diff --git a/Source/Router.swift b/Source/Router.swift index 74174a0..46c6e06 100644 --- a/Source/Router.swift +++ b/Source/Router.swift @@ -17,6 +17,6 @@ public protocol Router: AnyObject { extension Navigator: Router { public func route(_ url: URL) { - route(url, parameters: nil) + route(url, options: VisitOptions(action: .advance), parameters: nil) } } diff --git a/Source/Turbo/Navigator/Extensions/VisitProposalExtension.swift b/Source/Turbo/Navigator/Extensions/VisitProposalExtension.swift index c9fed46..4195c3c 100644 --- a/Source/Turbo/Navigator/Extensions/VisitProposalExtension.swift +++ b/Source/Turbo/Navigator/Extensions/VisitProposalExtension.swift @@ -59,4 +59,13 @@ public extension VisitProposal { return VisitableViewController.pathConfigurationIdentifier } + + /// Allows the proposal to change the animation status when pushing, popping or presenting. + var animated: Bool { + if let animated = parameters?["animated"] as? Bool { + return animated + } + + return true + } } diff --git a/Source/Turbo/Navigator/NavigationHierarchyController.swift b/Source/Turbo/Navigator/NavigationHierarchyController.swift index 5cfb930..f422fed 100644 --- a/Source/Turbo/Navigator/NavigationHierarchyController.swift +++ b/Source/Turbo/Navigator/NavigationHierarchyController.swift @@ -35,7 +35,7 @@ class NavigationHierarchyController { func route(controller: UIViewController, proposal: VisitProposal) { if let alert = controller as? UIAlertController { - presentAlert(alert) + presentAlert(alert, via: proposal) } else { if let visitable = controller as? Visitable { visitable.visitableView.allowsPullToRefresh = proposal.pullToRefreshEnabled @@ -45,15 +45,15 @@ class NavigationHierarchyController { case .default: navigate(with: controller, via: proposal) case .pop: - pop() + pop(via: proposal) case .replace: replace(with: controller, via: proposal) case .refresh: - refresh() + refresh(via: proposal) case .clearAll: - clearAll() + clearAll(via: proposal) case .replaceRoot: - replaceRoot(with: controller) + replaceRoot(with: controller, via: proposal) case .none: break // Do nothing. } @@ -69,11 +69,11 @@ class NavigationHierarchyController { private unowned let delegate: NavigationHierarchyControllerDelegate - private func presentAlert(_ alert: UIAlertController) { + private func presentAlert(_ alert: UIAlertController, via proposal: VisitProposal) { if navigationController.presentedViewController != nil { - modalNavigationController.present(alert, animated: true) + modalNavigationController.present(alert, animated: proposal.animated) } else { - navigationController.present(alert, animated: true) + navigationController.present(alert, animated: proposal.animated) } } @@ -83,7 +83,7 @@ class NavigationHierarchyController { if let visitable = controller as? Visitable { delegate.visit(visitable, on: .main, with: proposal.options) } - navigationController.dismiss(animated: true) + navigationController.dismiss(animated: proposal.animated) pushOrReplace(on: navigationController, with: controller, via: proposal) case .modal: if let visitable = controller as? Visitable { @@ -92,9 +92,9 @@ class NavigationHierarchyController { if navigationController.presentedViewController != nil, !modalNavigationController.isBeingDismissed { pushOrReplace(on: modalNavigationController, with: controller, via: proposal) } else { - modalNavigationController.setViewControllers([controller], animated: true) + modalNavigationController.setViewControllers([controller], animated: proposal.animated) modalNavigationController.setModalPresentationStyle(via: proposal) - navigationController.present(modalNavigationController, animated: true) + navigationController.present(modalNavigationController, animated: proposal.animated) } } } @@ -103,9 +103,9 @@ class NavigationHierarchyController { if visitingSamePage(on: navigationController, with: controller, via: proposal.url) { navigationController.replaceLastViewController(with: controller) } else if visitingPreviousPage(on: navigationController, with: controller, via: proposal.url) { - navigationController.popViewController(animated: true) + navigationController.popViewController(animated: proposal.animated) } else if proposal.options.action == .advance { - navigationController.pushViewController(controller, animated: true) + navigationController.pushViewController(controller, animated: proposal.animated) } else { navigationController.replaceLastViewController(with: controller) } @@ -130,15 +130,15 @@ class NavigationHierarchyController { return type(of: previousController) == type(of: controller) } - private func pop() { + private func pop(via proposal: VisitProposal) { if navigationController.presentedViewController != nil { if modalNavigationController.viewControllers.count == 1 { - navigationController.dismiss(animated: true) + navigationController.dismiss(animated: proposal.animated) } else { - modalNavigationController.popViewController(animated: true) + modalNavigationController.popViewController(animated: proposal.animated) } } else { - navigationController.popViewController(animated: true) + navigationController.popViewController(animated: proposal.animated) } } @@ -148,7 +148,7 @@ class NavigationHierarchyController { if let visitable = controller as? Visitable { delegate.visit(visitable, on: .main, with: proposal.options) } - navigationController.dismiss(animated: true) + navigationController.dismiss(animated: proposal.animated) navigationController.replaceLastViewController(with: controller) case .modal: if let visitable = controller as? Visitable { @@ -159,38 +159,38 @@ class NavigationHierarchyController { } else { modalNavigationController.setViewControllers([controller], animated: false) modalNavigationController.setModalPresentationStyle(via: proposal) - navigationController.present(modalNavigationController, animated: true) + navigationController.present(modalNavigationController, animated: proposal.animated) } } } - private func refresh() { + private func refresh(via proposal: VisitProposal) { if navigationController.presentedViewController != nil { if modalNavigationController.viewControllers.count == 1 { delegate.refresh(navigationStack: .main) - navigationController.dismiss(animated: true) + navigationController.dismiss(animated: proposal.animated) } else { delegate.refresh(navigationStack: .modal) - modalNavigationController.popViewController(animated: true) + modalNavigationController.popViewController(animated: proposal.animated) } } else { delegate.refresh(navigationStack: .main) - navigationController.popViewController(animated: true) + navigationController.popViewController(animated: proposal.animated) } } - private func clearAll() { + private func clearAll(via proposal: VisitProposal) { delegate.refresh(navigationStack: .main) - navigationController.dismiss(animated: true) - navigationController.popToRootViewController(animated: true) + navigationController.dismiss(animated: proposal.animated) + navigationController.popToRootViewController(animated: proposal.animated) } - private func replaceRoot(with controller: UIViewController) { + private func replaceRoot(with controller: UIViewController, via proposal: VisitProposal) { if let visitable = controller as? Visitable { delegate.visit(visitable, on: .main, with: .init(action: .replace)) } - navigationController.dismiss(animated: true) - navigationController.setViewControllers([controller], animated: true) + navigationController.dismiss(animated: proposal.animated) + navigationController.setViewControllers([controller], animated: proposal.animated) } } diff --git a/Source/Turbo/Navigator/Navigator.swift b/Source/Turbo/Navigator/Navigator.swift index ca328f3..bf790c2 100644 --- a/Source/Turbo/Navigator/Navigator.swift +++ b/Source/Turbo/Navigator/Navigator.swift @@ -11,6 +11,7 @@ public class Navigator { public unowned var delegate: NavigatorDelegate public var rootViewController: UINavigationController { hierarchyController.navigationController } + public var modalRootViewController: UINavigationController { hierarchyController.modalNavigationController } public var activeNavigationController: UINavigationController { hierarchyController.activeNavigationController } /// Set to handle customize behavior of the `WKUIDelegate`. @@ -42,11 +43,11 @@ public class Navigator { /// Convenience function to routing a proposal directly. /// /// - Parameter url: the URL to visit + /// - Parameter options: passed options will override default `advance` visit options /// - Parameter parameters: provide context relevant to `url` - public func route(_ url: URL, parameters: [String: Any]? = nil) { - let options = VisitOptions(action: .advance, response: nil) + public func route(_ url: URL, options: VisitOptions? = VisitOptions(action: .advance), parameters: [String: Any]? = nil) { let properties = session.pathConfiguration?.properties(for: url) ?? PathProperties() - route(VisitProposal(url: url, options: options, properties: properties, parameters: parameters)) + route(VisitProposal(url: url, options: options ?? .init(action: .advance), properties: properties)) } /// Transforms `VisitProposal` -> `UIViewController` diff --git a/Tests/Turbo/Navigator/NavigationHierarchyControllerTests.swift b/Tests/Turbo/Navigator/NavigationHierarchyControllerTests.swift index 2d81e0d..0df3d02 100644 --- a/Tests/Turbo/Navigator/NavigationHierarchyControllerTests.swift +++ b/Tests/Turbo/Navigator/NavigationHierarchyControllerTests.swift @@ -17,7 +17,7 @@ final class NavigationHierarchyControllerTests: XCTestCase { loadNavigationControllerInWindow() } - func test_default_default_default_pushesOnMainStack() { + func test_default_default_default_defaultOptionsParamater_pushesOnMainStack() { navigator.route(oneURL) XCTAssertEqual(navigationController.viewControllers.count, 1) XCTAssert(navigator.rootViewController.viewControllers.last is VisitableViewController) @@ -28,6 +28,17 @@ final class NavigationHierarchyControllerTests: XCTestCase { assertVisited(url: twoURL, on: .main) } + func test_default_default_default_nilOptionsParameter_pushesOnMainStack() { + navigator.route(oneURL) + XCTAssertEqual(navigationController.viewControllers.count, 1) + XCTAssert(navigator.rootViewController.viewControllers.last is VisitableViewController) + + navigator.route(twoURL, options: nil) + XCTAssertEqual(navigationController.viewControllers.count, 2) + XCTAssert(navigator.rootViewController.viewControllers.last is VisitableViewController) + assertVisited(url: twoURL, on: .main) + } + func test_default_default_default_visitingSamePage_replacesOnMainStack() { navigator.route(oneURL) XCTAssertEqual(navigationController.viewControllers.count, 1)