From db2a4543bd01874768d4f7b82ecfaefd2b88e133 Mon Sep 17 00:00:00 2001 From: Erik Drobne Date: Mon, 15 May 2023 10:47:05 +0200 Subject: [PATCH 01/17] Add support for custom transitions. --- .../Shapes/ShapesCoordinator.swift | 38 ++++++++++++- .../CoordinatorHostingController.swift | 25 +++++++++ .../NavigationController.swift | 53 +++++++++++++++++++ Sources/SwiftUICoordinator/Navigator.swift | 5 +- 4 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 Sources/SwiftUICoordinator/CoordinatorHostingController.swift create mode 100644 Sources/SwiftUICoordinator/NavigationController.swift diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesCoordinator.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesCoordinator.swift index 819ac09..f7a6290 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesCoordinator.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesCoordinator.swift @@ -20,10 +20,12 @@ class ShapesCoordinator: NSObject, Coordinator, Navigator { // MARK: - Initialization - init(navigationController: UINavigationController = .init(), startRoute: ShapesRoute? = nil) { - self.navigationController = navigationController + init(startRoute: ShapesRoute? = nil) { + self.navigationController = NavigationController() self.startRoute = startRoute super.init() + + setup() } func navigate(to route: NavigationRoute) { @@ -51,6 +53,10 @@ class ShapesCoordinator: NSObject, Coordinator, Navigator { } // MARK: - Private methods + + private func setup() { + (navigationController as? NavigationController)?.register(FadeAnimator()) + } private func makeSimpleShapesCoordinator() -> SimpleShapesCoordinator { let coordinator = SimpleShapesCoordinator(parent: self, navigationController: navigationController) @@ -83,3 +89,31 @@ extension ShapesCoordinator: RouterViewFactory { } } } + +class FadeAnimator: NSObject, Transition { + func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute) -> Bool { + return toRoute is CustomShapesRoute + } + + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return 0.3 // Set the duration of the fade animation + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + guard let toView = transitionContext.view(forKey: .to) else { + transitionContext.completeTransition(false) + return + } + + let containerView = transitionContext.containerView + toView.alpha = 0.0 + + containerView.addSubview(toView) + + UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { + toView.alpha = 1.0 + }) { _ in + transitionContext.completeTransition(!transitionContext.transitionWasCancelled) + } + } +} diff --git a/Sources/SwiftUICoordinator/CoordinatorHostingController.swift b/Sources/SwiftUICoordinator/CoordinatorHostingController.swift new file mode 100644 index 0000000..fde9061 --- /dev/null +++ b/Sources/SwiftUICoordinator/CoordinatorHostingController.swift @@ -0,0 +1,25 @@ +// +// CoordinatorHostingController.swift +// +// +// Created by Erik Drobne on 15/05/2023. +// + +import SwiftUI + +public protocol NavigationRouteHostingController { + var route: NavigationRoute { get } +} + +public class CoordinatorHostingController: UIHostingController, NavigationRouteHostingController { + public let route: NavigationRoute + + init(rootView: Content, route: NavigationRoute) { + self.route = route + super.init(rootView: rootView) + } + + @objc required dynamic init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Sources/SwiftUICoordinator/NavigationController.swift b/Sources/SwiftUICoordinator/NavigationController.swift new file mode 100644 index 0000000..81ad8cb --- /dev/null +++ b/Sources/SwiftUICoordinator/NavigationController.swift @@ -0,0 +1,53 @@ +// +// NavigationController.swift +// +// +// Created by Erik Drobne on 12/05/2023. +// + +import Foundation +import SwiftUI + +public class NavigationController: UINavigationController, UINavigationControllerDelegate { + + public private(set) var transitions = [Transition]() + + public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + + self.delegate = self + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public convenience init() { + self.init(nibName: nil, bundle: nil) + } + + public func navigationController(_ navigationController: UINavigationController, + animationControllerFor operation: UINavigationController.Operation, + from fromVC: UIViewController, + to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { + + for transition in transitions { + if let fromRoute = (fromVC as? NavigationRouteHostingController)?.route, + let toRoute = (toVC as? NavigationRouteHostingController)?.route { + if transition.isEligible(from: fromRoute,to: toRoute) { + return transition + } + } + } + + return nil + } + + public func register(_ transition: Transition) { + transitions.append(transition) + } +} + +public protocol Transition: UIViewControllerAnimatedTransitioning { + func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute) -> Bool +} diff --git a/Sources/SwiftUICoordinator/Navigator.swift b/Sources/SwiftUICoordinator/Navigator.swift index 0483ea6..94446b6 100644 --- a/Sources/SwiftUICoordinator/Navigator.swift +++ b/Sources/SwiftUICoordinator/Navigator.swift @@ -54,7 +54,10 @@ public extension Navigator where Self: Coordinator, Self: RouterViewFactory { } let viewWithCoordinator = view.environmentObject(self) - let viewController = UIHostingController(rootView: viewWithCoordinator) + let viewController = CoordinatorHostingController( + rootView: viewWithCoordinator, + route: route + ) switch route.transition { case .push(let animated): From 1ad942dc440914d3bfb2f78eeb808d4cb7ef26e8 Mon Sep 17 00:00:00 2001 From: Erik Drobne Date: Mon, 15 May 2023 13:42:53 +0200 Subject: [PATCH 02/17] Add RouterProvider protocol and RoutingHostingController. --- .../SwiftUICoordinator/NavigationController.swift | 11 +++++------ Sources/SwiftUICoordinator/Navigator.swift | 2 +- ...Controller.swift => RouteHostingController.swift} | 8 ++------ Sources/SwiftUICoordinator/RouteProvider.swift | 12 ++++++++++++ 4 files changed, 20 insertions(+), 13 deletions(-) rename Sources/SwiftUICoordinator/{CoordinatorHostingController.swift => RouteHostingController.swift} (59%) create mode 100644 Sources/SwiftUICoordinator/RouteProvider.swift diff --git a/Sources/SwiftUICoordinator/NavigationController.swift b/Sources/SwiftUICoordinator/NavigationController.swift index 81ad8cb..1eef913 100644 --- a/Sources/SwiftUICoordinator/NavigationController.swift +++ b/Sources/SwiftUICoordinator/NavigationController.swift @@ -27,14 +27,13 @@ public class NavigationController: UINavigationController, UINavigationControlle } public func navigationController(_ navigationController: UINavigationController, - animationControllerFor operation: UINavigationController.Operation, - from fromVC: UIViewController, - to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { + animationControllerFor operation: UINavigationController.Operation, + from fromVC: UIViewController, + to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { for transition in transitions { - if let fromRoute = (fromVC as? NavigationRouteHostingController)?.route, - let toRoute = (toVC as? NavigationRouteHostingController)?.route { - if transition.isEligible(from: fromRoute,to: toRoute) { + if let from = (fromVC as? RouteProvider)?.route, let to = (toVC as? RouteProvider)?.route { + if transition.isEligible(from: from,to: to) { return transition } } diff --git a/Sources/SwiftUICoordinator/Navigator.swift b/Sources/SwiftUICoordinator/Navigator.swift index 94446b6..1b4e3ec 100644 --- a/Sources/SwiftUICoordinator/Navigator.swift +++ b/Sources/SwiftUICoordinator/Navigator.swift @@ -54,7 +54,7 @@ public extension Navigator where Self: Coordinator, Self: RouterViewFactory { } let viewWithCoordinator = view.environmentObject(self) - let viewController = CoordinatorHostingController( + let viewController = RouteHostingController( rootView: viewWithCoordinator, route: route ) diff --git a/Sources/SwiftUICoordinator/CoordinatorHostingController.swift b/Sources/SwiftUICoordinator/RouteHostingController.swift similarity index 59% rename from Sources/SwiftUICoordinator/CoordinatorHostingController.swift rename to Sources/SwiftUICoordinator/RouteHostingController.swift index fde9061..179fef5 100644 --- a/Sources/SwiftUICoordinator/CoordinatorHostingController.swift +++ b/Sources/SwiftUICoordinator/RouteHostingController.swift @@ -1,5 +1,5 @@ // -// CoordinatorHostingController.swift +// RouteHostingController.swift // // // Created by Erik Drobne on 15/05/2023. @@ -7,11 +7,7 @@ import SwiftUI -public protocol NavigationRouteHostingController { - var route: NavigationRoute { get } -} - -public class CoordinatorHostingController: UIHostingController, NavigationRouteHostingController { +public class RouteHostingController: UIHostingController, RouteProvider { public let route: NavigationRoute init(rootView: Content, route: NavigationRoute) { diff --git a/Sources/SwiftUICoordinator/RouteProvider.swift b/Sources/SwiftUICoordinator/RouteProvider.swift new file mode 100644 index 0000000..4647f9f --- /dev/null +++ b/Sources/SwiftUICoordinator/RouteProvider.swift @@ -0,0 +1,12 @@ +// +// RouteProvider.swift +// +// +// Created by Erik Drobne on 15/05/2023. +// + +import Foundation + +public protocol RouteProvider { + var route: NavigationRoute { get } +} From cc0dd884a3f30d3419eb290c7ef4e29a43b29f3f Mon Sep 17 00:00:00 2001 From: Erik Drobne Date: Mon, 15 May 2023 13:51:28 +0200 Subject: [PATCH 03/17] Modify source structure. --- .../{ => Coordinator}/Coordinator.swift | 0 .../{ => Coordinator}/Navigator.swift | 0 .../{ => Coordinator}/NavigatorError.swift | 0 .../SwiftUICoordinator/NavigationController.swift | 4 ---- .../{ => Routing}/NavigationRoute.swift | 0 .../{ => Routing}/RouteHostingController.swift | 0 .../{ => Routing}/RouteProvider.swift | 0 .../{ => Routing}/RouterViewFactory.swift | 0 .../{ => Transitions}/NavigationTransition.swift | 0 .../SwiftUICoordinator/Transitions/Transition.swift | 12 ++++++++++++ 10 files changed, 12 insertions(+), 4 deletions(-) rename Sources/SwiftUICoordinator/{ => Coordinator}/Coordinator.swift (100%) rename Sources/SwiftUICoordinator/{ => Coordinator}/Navigator.swift (100%) rename Sources/SwiftUICoordinator/{ => Coordinator}/NavigatorError.swift (100%) rename Sources/SwiftUICoordinator/{ => Routing}/NavigationRoute.swift (100%) rename Sources/SwiftUICoordinator/{ => Routing}/RouteHostingController.swift (100%) rename Sources/SwiftUICoordinator/{ => Routing}/RouteProvider.swift (100%) rename Sources/SwiftUICoordinator/{ => Routing}/RouterViewFactory.swift (100%) rename Sources/SwiftUICoordinator/{ => Transitions}/NavigationTransition.swift (100%) create mode 100644 Sources/SwiftUICoordinator/Transitions/Transition.swift diff --git a/Sources/SwiftUICoordinator/Coordinator.swift b/Sources/SwiftUICoordinator/Coordinator/Coordinator.swift similarity index 100% rename from Sources/SwiftUICoordinator/Coordinator.swift rename to Sources/SwiftUICoordinator/Coordinator/Coordinator.swift diff --git a/Sources/SwiftUICoordinator/Navigator.swift b/Sources/SwiftUICoordinator/Coordinator/Navigator.swift similarity index 100% rename from Sources/SwiftUICoordinator/Navigator.swift rename to Sources/SwiftUICoordinator/Coordinator/Navigator.swift diff --git a/Sources/SwiftUICoordinator/NavigatorError.swift b/Sources/SwiftUICoordinator/Coordinator/NavigatorError.swift similarity index 100% rename from Sources/SwiftUICoordinator/NavigatorError.swift rename to Sources/SwiftUICoordinator/Coordinator/NavigatorError.swift diff --git a/Sources/SwiftUICoordinator/NavigationController.swift b/Sources/SwiftUICoordinator/NavigationController.swift index 1eef913..5a5e19b 100644 --- a/Sources/SwiftUICoordinator/NavigationController.swift +++ b/Sources/SwiftUICoordinator/NavigationController.swift @@ -46,7 +46,3 @@ public class NavigationController: UINavigationController, UINavigationControlle transitions.append(transition) } } - -public protocol Transition: UIViewControllerAnimatedTransitioning { - func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute) -> Bool -} diff --git a/Sources/SwiftUICoordinator/NavigationRoute.swift b/Sources/SwiftUICoordinator/Routing/NavigationRoute.swift similarity index 100% rename from Sources/SwiftUICoordinator/NavigationRoute.swift rename to Sources/SwiftUICoordinator/Routing/NavigationRoute.swift diff --git a/Sources/SwiftUICoordinator/RouteHostingController.swift b/Sources/SwiftUICoordinator/Routing/RouteHostingController.swift similarity index 100% rename from Sources/SwiftUICoordinator/RouteHostingController.swift rename to Sources/SwiftUICoordinator/Routing/RouteHostingController.swift diff --git a/Sources/SwiftUICoordinator/RouteProvider.swift b/Sources/SwiftUICoordinator/Routing/RouteProvider.swift similarity index 100% rename from Sources/SwiftUICoordinator/RouteProvider.swift rename to Sources/SwiftUICoordinator/Routing/RouteProvider.swift diff --git a/Sources/SwiftUICoordinator/RouterViewFactory.swift b/Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift similarity index 100% rename from Sources/SwiftUICoordinator/RouterViewFactory.swift rename to Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift diff --git a/Sources/SwiftUICoordinator/NavigationTransition.swift b/Sources/SwiftUICoordinator/Transitions/NavigationTransition.swift similarity index 100% rename from Sources/SwiftUICoordinator/NavigationTransition.swift rename to Sources/SwiftUICoordinator/Transitions/NavigationTransition.swift diff --git a/Sources/SwiftUICoordinator/Transitions/Transition.swift b/Sources/SwiftUICoordinator/Transitions/Transition.swift new file mode 100644 index 0000000..b04bc68 --- /dev/null +++ b/Sources/SwiftUICoordinator/Transitions/Transition.swift @@ -0,0 +1,12 @@ +// +// Transition.swift +// +// +// Created by Erik Drobne on 15/05/2023. +// + +import UIKit + +public protocol Transition: UIViewControllerAnimatedTransitioning { + func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute) -> Bool +} From bbde9f234d4acbfc3bfe3eadf4ba2ffd9cb6fc5a Mon Sep 17 00:00:00 2001 From: Erik Drobne Date: Mon, 15 May 2023 15:52:21 +0200 Subject: [PATCH 04/17] Use NavigationController instead of UINavigationController. --- .../CustomShapes/CustomShapesCoordinator.swift | 4 ++-- .../Coordinators/CustomShapes/CustomShapesRoute.swift | 7 ++++++- .../Coordinators/Shapes/ShapesCoordinator.swift | 9 +++++---- .../Coordinators/Shapes/ShapesRoute.swift | 2 +- .../SimpleShapes/SimpleShapesCoordinator.swift | 4 ++-- .../Coordinators/SimpleShapes/SimpleShapesRoute.swift | 2 +- .../Views/CustomShapesView.swift | 2 +- .../Views/SimpleShapesView.swift | 2 +- Sources/SwiftUICoordinator/Coordinator/Navigator.swift | 4 ++-- .../{ => Routing}/NavigationController.swift | 8 ++++++++ .../Transitions/NavigationTransition.swift | 4 ++-- 11 files changed, 31 insertions(+), 17 deletions(-) rename Sources/SwiftUICoordinator/{ => Routing}/NavigationController.swift (81%) diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/CustomShapes/CustomShapesCoordinator.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/CustomShapes/CustomShapesCoordinator.swift index 4473cf4..e708af6 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/CustomShapes/CustomShapesCoordinator.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/CustomShapes/CustomShapesCoordinator.swift @@ -16,12 +16,12 @@ class CustomShapesCoordinator: NSObject, Coordinator, Navigator { weak var parent: Coordinator? = nil var childCoordinators = [Coordinator]() - var navigationController: UINavigationController + var navigationController: NavigationController let startRoute: CustomShapesRoute? // MARK: - Initialization - init(parent: Coordinator?, navigationController: UINavigationController = .init(), startRoute: CustomShapesRoute? = .customShapes) { + init(parent: Coordinator?, navigationController: NavigationController, startRoute: CustomShapesRoute? = .customShapes) { self.parent = parent self.navigationController = navigationController self.startRoute = startRoute diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/CustomShapes/CustomShapesRoute.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/CustomShapes/CustomShapesRoute.swift index a3b2c9f..258b1e7 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/CustomShapes/CustomShapesRoute.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/CustomShapes/CustomShapesRoute.swift @@ -28,6 +28,11 @@ enum CustomShapesRoute: NavigationRoute { } var transition: NavigationTransition? { - return .push() + switch self { + case .tower: + return .present(animated: true, modalPresentationStyle: .fullScreen) + default: + return .push(animated: true) + } } } diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesCoordinator.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesCoordinator.swift index f7a6290..7cf4764 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesCoordinator.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesCoordinator.swift @@ -13,9 +13,9 @@ class ShapesCoordinator: NSObject, Coordinator, Navigator { // MARK: - Internal properties /// Root coordinator doesn't have a parent. - weak var parent: Coordinator? = nil + let parent: Coordinator? = nil var childCoordinators = [Coordinator]() - var navigationController: UINavigationController + var navigationController: NavigationController let startRoute: ShapesRoute? // MARK: - Initialization @@ -55,7 +55,7 @@ class ShapesCoordinator: NSObject, Coordinator, Navigator { // MARK: - Private methods private func setup() { - (navigationController as? NavigationController)?.register(FadeAnimator()) + navigationController.register(FadeAnimator()) } private func makeSimpleShapesCoordinator() -> SimpleShapesCoordinator { @@ -92,7 +92,8 @@ extension ShapesCoordinator: RouterViewFactory { class FadeAnimator: NSObject, Transition { func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute) -> Bool { - return toRoute is CustomShapesRoute + return (fromRoute as? CustomShapesRoute == .customShapes && toRoute as? CustomShapesRoute == .star) || + (fromRoute as? CustomShapesRoute == .customShapes && toRoute as? CustomShapesRoute == .tower) } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesRoute.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesRoute.swift index c3d687f..3102389 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesRoute.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesRoute.swift @@ -29,7 +29,7 @@ enum ShapesRoute: NavigationRoute { /// We have to pass nil for the route presenting a child coordinator. return nil default: - return .push() + return .push(animated: true) } } } diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/SimpleShapes/SimpleShapesCoordinator.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/SimpleShapes/SimpleShapesCoordinator.swift index 8c6f403..636782a 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/SimpleShapes/SimpleShapesCoordinator.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/SimpleShapes/SimpleShapesCoordinator.swift @@ -14,12 +14,12 @@ class SimpleShapesCoordinator: NSObject, Coordinator, Navigator { weak var parent: Coordinator? = nil var childCoordinators = [Coordinator]() - var navigationController: UINavigationController + var navigationController: NavigationController let startRoute: SimpleShapesRoute? // MARK: - Initialization - init(parent: Coordinator?, navigationController: UINavigationController = .init(), startRoute: SimpleShapesRoute? = .simpleShapes) { + init(parent: Coordinator?, navigationController: NavigationController, startRoute: SimpleShapesRoute? = .simpleShapes) { self.parent = parent self.navigationController = navigationController self.startRoute = startRoute diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/SimpleShapes/SimpleShapesRoute.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/SimpleShapes/SimpleShapesRoute.swift index 337d152..3f60978 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/SimpleShapes/SimpleShapesRoute.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/SimpleShapes/SimpleShapesRoute.swift @@ -34,6 +34,6 @@ enum SimpleShapesRoute: NavigationRoute { } var transition: NavigationTransition? { - return .push() + return .push(animated: false) } } diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Views/CustomShapesView.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Views/CustomShapesView.swift index 6202e24..fc8d2f0 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Views/CustomShapesView.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Views/CustomShapesView.swift @@ -56,7 +56,7 @@ extension CustomShapesView { } struct CustomShapesView_Previews: PreviewProvider { - static let coordinator = CustomShapesCoordinator(parent: nil) + static let coordinator = CustomShapesCoordinator(parent: nil, navigationController: .init()) static var previews: some View { CustomShapesView() diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Views/SimpleShapesView.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Views/SimpleShapesView.swift index 7ccc286..c16c29e 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Views/SimpleShapesView.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Views/SimpleShapesView.swift @@ -74,7 +74,7 @@ extension SimpleShapesView { } struct SimpleShapesView_Previews: PreviewProvider { - static let coordinator = SimpleShapesCoordinator(parent: nil) + static let coordinator = SimpleShapesCoordinator(parent: nil, navigationController: .init()) static var previews: some View { SimpleShapesView() diff --git a/Sources/SwiftUICoordinator/Coordinator/Navigator.swift b/Sources/SwiftUICoordinator/Coordinator/Navigator.swift index 1b4e3ec..edeaf32 100644 --- a/Sources/SwiftUICoordinator/Coordinator/Navigator.swift +++ b/Sources/SwiftUICoordinator/Coordinator/Navigator.swift @@ -13,7 +13,7 @@ public typealias Routing = Coordinator & Navigator public protocol Navigator: ObservableObject { associatedtype Route: NavigationRoute - var navigationController: UINavigationController { get set } + var navigationController: NavigationController { get set } var startRoute: Route? { get } func start() throws @@ -101,7 +101,7 @@ public extension Navigator where Self: Coordinator, Self: RouterViewFactory { let view = self.view(for: route).ifLet(route.title) { view, value in view.navigationTitle(value) } - return UIHostingController(rootView: view.environmentObject(self)) + return RouteHostingController(rootView: view.environmentObject(self), route: route) }) } diff --git a/Sources/SwiftUICoordinator/NavigationController.swift b/Sources/SwiftUICoordinator/Routing/NavigationController.swift similarity index 81% rename from Sources/SwiftUICoordinator/NavigationController.swift rename to Sources/SwiftUICoordinator/Routing/NavigationController.swift index 5a5e19b..b6e8789 100644 --- a/Sources/SwiftUICoordinator/NavigationController.swift +++ b/Sources/SwiftUICoordinator/Routing/NavigationController.swift @@ -42,6 +42,14 @@ public class NavigationController: UINavigationController, UINavigationControlle return nil } + func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { + return nil + } + + func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { + return nil + } + public func register(_ transition: Transition) { transitions.append(transition) } diff --git a/Sources/SwiftUICoordinator/Transitions/NavigationTransition.swift b/Sources/SwiftUICoordinator/Transitions/NavigationTransition.swift index a30d1a1..77dc625 100644 --- a/Sources/SwiftUICoordinator/Transitions/NavigationTransition.swift +++ b/Sources/SwiftUICoordinator/Transitions/NavigationTransition.swift @@ -9,9 +9,9 @@ import Foundation import SwiftUI public enum NavigationTransition { - case push(animated: Bool = true) + case push(animated: Bool) case present( - animated: Bool = true, + animated: Bool, modalPresentationStyle: UIModalPresentationStyle = .automatic, completion: (() -> Void)? = nil ) From 8a9b2a693b163da0c3e0011dd466598ffe979aa7 Mon Sep 17 00:00:00 2001 From: Erik Drobne Date: Mon, 15 May 2023 16:40:09 +0200 Subject: [PATCH 05/17] Refactor NavigationTransition to TransitionAction. --- .../project.pbxproj | 12 ++++++ .../Shapes/ShapesCoordinator.swift | 31 +-------------- .../SimpleShapes/SimpleShapesRoute.swift | 2 +- .../Transitions/FadeTransition.swift | 38 +++++++++++++++++++ .../{Coordinator => }/Coordinator.swift | 0 .../Navigator.swift | 2 +- .../NavigatorError.swift | 0 .../Routing/NavigationController.swift | 6 +++ .../Routing/NavigationRoute.swift | 6 +-- .../Transition.swift | 0 .../TransitionAction.swift} | 4 +- 11 files changed, 64 insertions(+), 37 deletions(-) create mode 100644 Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Transitions/FadeTransition.swift rename Sources/SwiftUICoordinator/{Coordinator => }/Coordinator.swift (100%) rename Sources/SwiftUICoordinator/{Coordinator => Navigator}/Navigator.swift (99%) rename Sources/SwiftUICoordinator/{Coordinator => Navigator}/NavigatorError.swift (100%) rename Sources/SwiftUICoordinator/{Transitions => Transition}/Transition.swift (100%) rename Sources/SwiftUICoordinator/{Transitions/NavigationTransition.swift => Transition/TransitionAction.swift} (81%) diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample.xcodeproj/project.pbxproj b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample.xcodeproj/project.pbxproj index 4d387d9..2abe47e 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample.xcodeproj/project.pbxproj +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 1732C9FC29A6607500C2BC1F /* SwiftUICoordinatorExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1732C9FB29A6607500C2BC1F /* SwiftUICoordinatorExampleApp.swift */; }; 1732CA0029A6607800C2BC1F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1732C9FF29A6607800C2BC1F /* Assets.xcassets */; }; 1732CA0329A6607800C2BC1F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1732CA0229A6607800C2BC1F /* Preview Assets.xcassets */; }; + 17360A412A1275D600DB2296 /* FadeTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17360A402A1275D600DB2296 /* FadeTransition.swift */; }; 176F3CB529B8BF71009C4987 /* ShapesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176F3CB429B8BF71009C4987 /* ShapesView.swift */; }; 176F3CB829B8C05B009C4987 /* ShapesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176F3CB729B8C05B009C4987 /* ShapesCoordinator.swift */; }; 176F3CBB29B8C162009C4987 /* ShapesRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 176F3CBA29B8C162009C4987 /* ShapesRoute.swift */; }; @@ -30,6 +31,7 @@ 1732C9FB29A6607500C2BC1F /* SwiftUICoordinatorExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUICoordinatorExampleApp.swift; sourceTree = ""; }; 1732C9FF29A6607800C2BC1F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 1732CA0229A6607800C2BC1F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 17360A402A1275D600DB2296 /* FadeTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FadeTransition.swift; sourceTree = ""; }; 176F3CB429B8BF71009C4987 /* ShapesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapesView.swift; sourceTree = ""; }; 176F3CB729B8C05B009C4987 /* ShapesCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapesCoordinator.swift; sourceTree = ""; }; 176F3CBA29B8C162009C4987 /* ShapesRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShapesRoute.swift; sourceTree = ""; }; @@ -81,6 +83,7 @@ 1732C9FB29A6607500C2BC1F /* SwiftUICoordinatorExampleApp.swift */, 176F3CB629B8C01E009C4987 /* Coordinators */, 176F3CB329B8BD92009C4987 /* Views */, + 17360A3F2A1275C000DB2296 /* Transitions */, 1732C9FF29A6607800C2BC1F /* Assets.xcassets */, 1732CA0129A6607800C2BC1F /* Preview Content */, ); @@ -95,6 +98,14 @@ path = "Preview Content"; sourceTree = ""; }; + 17360A3F2A1275C000DB2296 /* Transitions */ = { + isa = PBXGroup; + children = ( + 17360A402A1275D600DB2296 /* FadeTransition.swift */, + ); + path = Transitions; + sourceTree = ""; + }; 176F3CB329B8BD92009C4987 /* Views */ = { isa = PBXGroup; children = ( @@ -241,6 +252,7 @@ 17F1184529CC8CF6004755DB /* Star.swift in Sources */, 176F3CB829B8C05B009C4987 /* ShapesCoordinator.swift in Sources */, 176F3CB529B8BF71009C4987 /* ShapesView.swift in Sources */, + 17360A412A1275D600DB2296 /* FadeTransition.swift in Sources */, 17F1183D29CC668F004755DB /* SimpleShapesRoute.swift in Sources */, 17F1183529CC63B1004755DB /* SimpleShapesView.swift in Sources */, 176F3CBB29B8C162009C4987 /* ShapesRoute.swift in Sources */, diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesCoordinator.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesCoordinator.swift index 7cf4764..0fa6fc8 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesCoordinator.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesCoordinator.swift @@ -55,7 +55,7 @@ class ShapesCoordinator: NSObject, Coordinator, Navigator { // MARK: - Private methods private func setup() { - navigationController.register(FadeAnimator()) + navigationController.register(FadeTransition()) } private func makeSimpleShapesCoordinator() -> SimpleShapesCoordinator { @@ -89,32 +89,3 @@ extension ShapesCoordinator: RouterViewFactory { } } } - -class FadeAnimator: NSObject, Transition { - func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute) -> Bool { - return (fromRoute as? CustomShapesRoute == .customShapes && toRoute as? CustomShapesRoute == .star) || - (fromRoute as? CustomShapesRoute == .customShapes && toRoute as? CustomShapesRoute == .tower) - } - - func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return 0.3 // Set the duration of the fade animation - } - - func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { - guard let toView = transitionContext.view(forKey: .to) else { - transitionContext.completeTransition(false) - return - } - - let containerView = transitionContext.containerView - toView.alpha = 0.0 - - containerView.addSubview(toView) - - UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { - toView.alpha = 1.0 - }) { _ in - transitionContext.completeTransition(!transitionContext.transitionWasCancelled) - } - } -} diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/SimpleShapes/SimpleShapesRoute.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/SimpleShapes/SimpleShapesRoute.swift index 3f60978..e1e9fc0 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/SimpleShapes/SimpleShapesRoute.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/SimpleShapes/SimpleShapesRoute.swift @@ -34,6 +34,6 @@ enum SimpleShapesRoute: NavigationRoute { } var transition: NavigationTransition? { - return .push(animated: false) + return .push(animated: true) } } diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Transitions/FadeTransition.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Transitions/FadeTransition.swift new file mode 100644 index 0000000..09cca12 --- /dev/null +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Transitions/FadeTransition.swift @@ -0,0 +1,38 @@ +// +// FadeTransition.swift +// SwiftUICoordinatorExample +// +// Created by Erik Drobne on 15/05/2023. +// + +import SwiftUI +import SwiftUICoordinator + +class FadeTransition: NSObject, Transition { + func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute) -> Bool { + return (fromRoute as? CustomShapesRoute == .customShapes && toRoute as? CustomShapesRoute == .star) || + (fromRoute as? CustomShapesRoute == .customShapes && toRoute as? CustomShapesRoute == .tower) + } + + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return 0.3 // Set the duration of the fade animation + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + guard let toView = transitionContext.view(forKey: .to) else { + transitionContext.completeTransition(false) + return + } + + let containerView = transitionContext.containerView + toView.alpha = 0.0 + + containerView.addSubview(toView) + + UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { + toView.alpha = 1.0 + }) { _ in + transitionContext.completeTransition(!transitionContext.transitionWasCancelled) + } + } +} diff --git a/Sources/SwiftUICoordinator/Coordinator/Coordinator.swift b/Sources/SwiftUICoordinator/Coordinator.swift similarity index 100% rename from Sources/SwiftUICoordinator/Coordinator/Coordinator.swift rename to Sources/SwiftUICoordinator/Coordinator.swift diff --git a/Sources/SwiftUICoordinator/Coordinator/Navigator.swift b/Sources/SwiftUICoordinator/Navigator/Navigator.swift similarity index 99% rename from Sources/SwiftUICoordinator/Coordinator/Navigator.swift rename to Sources/SwiftUICoordinator/Navigator/Navigator.swift index edeaf32..e98f36c 100644 --- a/Sources/SwiftUICoordinator/Coordinator/Navigator.swift +++ b/Sources/SwiftUICoordinator/Navigator/Navigator.swift @@ -59,7 +59,7 @@ public extension Navigator where Self: Coordinator, Self: RouterViewFactory { route: route ) - switch route.transition { + switch route.action { case .push(let animated): navigationController.pushViewController(viewController, animated: animated) case .present(let animated, let modalPresentationStyle, let completion): diff --git a/Sources/SwiftUICoordinator/Coordinator/NavigatorError.swift b/Sources/SwiftUICoordinator/Navigator/NavigatorError.swift similarity index 100% rename from Sources/SwiftUICoordinator/Coordinator/NavigatorError.swift rename to Sources/SwiftUICoordinator/Navigator/NavigatorError.swift diff --git a/Sources/SwiftUICoordinator/Routing/NavigationController.swift b/Sources/SwiftUICoordinator/Routing/NavigationController.swift index b6e8789..4a3bca9 100644 --- a/Sources/SwiftUICoordinator/Routing/NavigationController.swift +++ b/Sources/SwiftUICoordinator/Routing/NavigationController.swift @@ -51,6 +51,12 @@ public class NavigationController: UINavigationController, UINavigationControlle } public func register(_ transition: Transition) { + guard transitions.first(where: { element in + return type(of: element) == type(of: transition) + }) == nil else { + return + } + transitions.append(transition) } } diff --git a/Sources/SwiftUICoordinator/Routing/NavigationRoute.swift b/Sources/SwiftUICoordinator/Routing/NavigationRoute.swift index ab832c4..8eaf821 100644 --- a/Sources/SwiftUICoordinator/Routing/NavigationRoute.swift +++ b/Sources/SwiftUICoordinator/Routing/NavigationRoute.swift @@ -11,7 +11,7 @@ import Foundation public protocol NavigationRoute { /// This title can be used to set the navigation bar title when the route is shown. var title: String? { get } - /// The type of transition to be used when the route is shown. - /// This can be a push transition, a modal presentation, or `nil` (for child coordinators). - var transition: NavigationTransition? { get } + /// Transition action to be used when the route is shown. + /// This can be a push action, a modal presentation, or `nil` (for child coordinators). + var action: TransitionAction? { get } } diff --git a/Sources/SwiftUICoordinator/Transitions/Transition.swift b/Sources/SwiftUICoordinator/Transition/Transition.swift similarity index 100% rename from Sources/SwiftUICoordinator/Transitions/Transition.swift rename to Sources/SwiftUICoordinator/Transition/Transition.swift diff --git a/Sources/SwiftUICoordinator/Transitions/NavigationTransition.swift b/Sources/SwiftUICoordinator/Transition/TransitionAction.swift similarity index 81% rename from Sources/SwiftUICoordinator/Transitions/NavigationTransition.swift rename to Sources/SwiftUICoordinator/Transition/TransitionAction.swift index 77dc625..e88de36 100644 --- a/Sources/SwiftUICoordinator/Transitions/NavigationTransition.swift +++ b/Sources/SwiftUICoordinator/Transition/TransitionAction.swift @@ -1,5 +1,5 @@ // -// NavigationTransition.swift +// TransitionAction.swift // // // Created by Erik Drobne on 12/12/2022. @@ -8,7 +8,7 @@ import Foundation import SwiftUI -public enum NavigationTransition { +public enum TransitionAction { case push(animated: Bool) case present( animated: Bool, From d6875e671f114be7759c6c2e0b6d25b7423681ed Mon Sep 17 00:00:00 2001 From: Erik Drobne Date: Mon, 15 May 2023 16:41:57 +0200 Subject: [PATCH 06/17] Update folder structure. --- Sources/SwiftUICoordinator/{ => Coordinator}/Coordinator.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Sources/SwiftUICoordinator/{ => Coordinator}/Coordinator.swift (100%) diff --git a/Sources/SwiftUICoordinator/Coordinator.swift b/Sources/SwiftUICoordinator/Coordinator/Coordinator.swift similarity index 100% rename from Sources/SwiftUICoordinator/Coordinator.swift rename to Sources/SwiftUICoordinator/Coordinator/Coordinator.swift From 9eec4fb8de7c803aa43c802ecbd0fca5c911ca4e Mon Sep 17 00:00:00 2001 From: Erik Drobne Date: Mon, 15 May 2023 16:59:16 +0200 Subject: [PATCH 07/17] Fix build. --- .../CustomShapes/CustomShapesRoute.swift | 2 +- .../Coordinators/Shapes/ShapesRoute.swift | 2 +- .../SimpleShapes/SimpleShapesRoute.swift | 2 +- .../Coordinator/Coordinator.swift | 6 +++ .../Navigator/Navigator.swift | 7 ++++ .../Routing/NavigationController.swift | 40 ++++++++++--------- .../Routing/RouteHostingController.swift | 6 +++ .../Routing/RouterViewFactory.swift | 1 + .../Transition/Transition.swift | 3 +- Sources/SwiftUICoordinator/Utilities.swift | 1 + 10 files changed, 47 insertions(+), 23 deletions(-) diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/CustomShapes/CustomShapesRoute.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/CustomShapes/CustomShapesRoute.swift index 258b1e7..aeb22bb 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/CustomShapes/CustomShapesRoute.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/CustomShapes/CustomShapesRoute.swift @@ -27,7 +27,7 @@ enum CustomShapesRoute: NavigationRoute { } } - var transition: NavigationTransition? { + var action: TransitionAction? { switch self { case .tower: return .present(animated: true, modalPresentationStyle: .fullScreen) diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesRoute.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesRoute.swift index 3102389..714d7e6 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesRoute.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesRoute.swift @@ -23,7 +23,7 @@ enum ShapesRoute: NavigationRoute { } } - var transition: NavigationTransition? { + var action: TransitionAction? { switch self { case .simpleShapes: /// We have to pass nil for the route presenting a child coordinator. diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/SimpleShapes/SimpleShapesRoute.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/SimpleShapes/SimpleShapesRoute.swift index e1e9fc0..2d9fbdf 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/SimpleShapes/SimpleShapesRoute.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/SimpleShapes/SimpleShapesRoute.swift @@ -33,7 +33,7 @@ enum SimpleShapesRoute: NavigationRoute { } } - var transition: NavigationTransition? { + var action: TransitionAction? { return .push(animated: true) } } diff --git a/Sources/SwiftUICoordinator/Coordinator/Coordinator.swift b/Sources/SwiftUICoordinator/Coordinator/Coordinator.swift index 70acc64..50c326f 100644 --- a/Sources/SwiftUICoordinator/Coordinator/Coordinator.swift +++ b/Sources/SwiftUICoordinator/Coordinator/Coordinator.swift @@ -5,6 +5,7 @@ // Created by Erik Drobne on 12/12/2022. // +import Foundation import SwiftUI @MainActor @@ -17,7 +18,12 @@ public protocol Coordinator: AnyObject { func finish() } +// MARK: - Extensions + public extension Coordinator { + + // MARK: - Public methods + func finish() { parent?.childCoordinators.removeAll(where: { $0 === self }) } diff --git a/Sources/SwiftUICoordinator/Navigator/Navigator.swift b/Sources/SwiftUICoordinator/Navigator/Navigator.swift index e98f36c..b4c6120 100644 --- a/Sources/SwiftUICoordinator/Navigator/Navigator.swift +++ b/Sources/SwiftUICoordinator/Navigator/Navigator.swift @@ -5,6 +5,7 @@ // Created by Erik Drobne on 12/12/2022. // +import Foundation import SwiftUI public typealias Routing = Coordinator & Navigator @@ -25,7 +26,11 @@ public protocol Navigator: ObservableObject { func dismiss(animated: Bool) } +// MARK: - Extensions + public extension Navigator where Self: Coordinator, Self: RouterViewFactory { + + // MARK: - Public properties var viewControllers: [UIViewController] { return navigationController.viewControllers @@ -38,6 +43,8 @@ public extension Navigator where Self: Coordinator, Self: RouterViewFactory { var visibleViewController: UIViewController? { return navigationController.visibleViewController } + + // MARK: - Public methods func start() throws { guard let route = startRoute else { diff --git a/Sources/SwiftUICoordinator/Routing/NavigationController.swift b/Sources/SwiftUICoordinator/Routing/NavigationController.swift index 4a3bca9..0dc6b90 100644 --- a/Sources/SwiftUICoordinator/Routing/NavigationController.swift +++ b/Sources/SwiftUICoordinator/Routing/NavigationController.swift @@ -8,10 +8,14 @@ import Foundation import SwiftUI -public class NavigationController: UINavigationController, UINavigationControllerDelegate { +public class NavigationController: UINavigationController { + + // MARK: - Properties public private(set) var transitions = [Transition]() + // MARK: - Initialization + public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) @@ -26,6 +30,22 @@ public class NavigationController: UINavigationController, UINavigationControlle self.init(nibName: nil, bundle: nil) } + // MARK: - Public methods + + public func register(_ transition: Transition) { + guard transitions.first(where: { element in + return type(of: element) == type(of: transition) + }) == nil else { + return + } + + transitions.append(transition) + } +} + +// MARK: - UINavigationControllerDelegate + +extension NavigationController: UINavigationControllerDelegate { public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, @@ -41,22 +61,4 @@ public class NavigationController: UINavigationController, UINavigationControlle return nil } - - func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return nil - } - - func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { - return nil - } - - public func register(_ transition: Transition) { - guard transitions.first(where: { element in - return type(of: element) == type(of: transition) - }) == nil else { - return - } - - transitions.append(transition) - } } diff --git a/Sources/SwiftUICoordinator/Routing/RouteHostingController.swift b/Sources/SwiftUICoordinator/Routing/RouteHostingController.swift index 179fef5..ad4ade5 100644 --- a/Sources/SwiftUICoordinator/Routing/RouteHostingController.swift +++ b/Sources/SwiftUICoordinator/Routing/RouteHostingController.swift @@ -5,11 +5,17 @@ // Created by Erik Drobne on 15/05/2023. // +import Foundation import SwiftUI public class RouteHostingController: UIHostingController, RouteProvider { + + // MARK: - Public properties + public let route: NavigationRoute + // MARK: - Initialization + init(rootView: Content, route: NavigationRoute) { self.route = route super.init(rootView: rootView) diff --git a/Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift b/Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift index 8a5ce71..154f320 100644 --- a/Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift +++ b/Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift @@ -5,6 +5,7 @@ // Created by Erik Drobne on 12/12/2022. // +import Foundation import SwiftUI @MainActor diff --git a/Sources/SwiftUICoordinator/Transition/Transition.swift b/Sources/SwiftUICoordinator/Transition/Transition.swift index b04bc68..572acdc 100644 --- a/Sources/SwiftUICoordinator/Transition/Transition.swift +++ b/Sources/SwiftUICoordinator/Transition/Transition.swift @@ -5,7 +5,8 @@ // Created by Erik Drobne on 15/05/2023. // -import UIKit +import Foundation +import SwiftUI public protocol Transition: UIViewControllerAnimatedTransitioning { func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute) -> Bool diff --git a/Sources/SwiftUICoordinator/Utilities.swift b/Sources/SwiftUICoordinator/Utilities.swift index 7effa32..5377914 100644 --- a/Sources/SwiftUICoordinator/Utilities.swift +++ b/Sources/SwiftUICoordinator/Utilities.swift @@ -5,6 +5,7 @@ // Created by Erik Drobne on 16/03/2023. // +import Foundation import SwiftUI public extension View { From f5e5b08441d37d533ec2af799ea27652d463923a Mon Sep 17 00:00:00 2001 From: Erik Drobne Date: Mon, 15 May 2023 17:11:21 +0200 Subject: [PATCH 08/17] Update readme. --- .../Transitions/FadeTransition.swift | 3 +- README.md | 56 +++++++++++++++++-- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Transitions/FadeTransition.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Transitions/FadeTransition.swift index 09cca12..146ab6d 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Transitions/FadeTransition.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Transitions/FadeTransition.swift @@ -10,8 +10,7 @@ import SwiftUICoordinator class FadeTransition: NSObject, Transition { func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute) -> Bool { - return (fromRoute as? CustomShapesRoute == .customShapes && toRoute as? CustomShapesRoute == .star) || - (fromRoute as? CustomShapesRoute == .customShapes && toRoute as? CustomShapesRoute == .tower) + return (fromRoute as? CustomShapesRoute == .customShapes && toRoute as? CustomShapesRoute == .star) } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { diff --git a/README.md b/README.md index 70b8278..a2a5446 100644 --- a/README.md +++ b/README.md @@ -51,9 +51,9 @@ This protocol defines the available routes for navigation within a coordinator f public protocol NavigationRoute { /// This title can be used to set the navigation bar title when the route is shown. var title: String? { get } - /// The type of transition to be used when the route is shown. - /// This can be a push transition, a modal presentation, or `nil` (for child coordinators). - var transition: NavigationTransition? { get } + /// Transition action to be used when the route is shown. + /// This can be a push action, a modal presentation, or `nil` (for child coordinators). + var action: TransitionAction? { get } } ``` @@ -78,7 +78,7 @@ public protocol Navigator: ObservableObject { /// This method is called when the navigator should start navigating. func start() throws /// Navigate to a specific route. - /// It creates a view for the route and adds it to the navigation stack using the specified transition. + /// It creates a view for the route and adds it to the navigation stack using the specified action (TransitionAction). func show(route: Route) throws /// Sets the navigation stack to a new array of routes. /// It can be useful if you need to reset the entire navigation stack to a new set of views. @@ -140,7 +140,7 @@ enum ShapesRoute: NavigationRoute { } } - var transition: NavigationTransition? { + var action: TransitionAction? { switch self { case .simpleShapes: // We have to pass nil for the route presenting a child coordinator. @@ -293,6 +293,52 @@ struct ShapesView: View { } ``` +### Custom transitions + +Create custom transition. + +```Swift +class FadeTransition: NSObject, Transition { + func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute) -> Bool { + return (fromRoute as? CustomShapesRoute == .customShapes && toRoute as? CustomShapesRoute == .star) + } + + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return 0.3 // Set the duration of the fade animation + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + guard let toView = transitionContext.view(forKey: .to) else { + transitionContext.completeTransition(false) + return + } + + let containerView = transitionContext.containerView + toView.alpha = 0.0 + + containerView.addSubview(toView) + + UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { + toView.alpha = 1.0 + }) { _ in + transitionContext.completeTransition(!transitionContext.transitionWasCancelled) + } + } +} +``` + +Register transition in the coordinator initializer. + +```Swift + init(startRoute: ShapesRoute? = nil) { + self.navigationController = NavigationController() + self.startRoute = startRoute + super.init() + + navigationController.register(FadeTransition()) + } +``` + ## 📒 Example project For better understanding, I recommend that you take a look at the example project located in the `SwiftUICoordinatorExample` folder. From 11d70d2eebf2396cd158400d44d91ba6d87bd313 Mon Sep 17 00:00:00 2001 From: Erik Drobne Date: Mon, 15 May 2023 17:20:09 +0200 Subject: [PATCH 09/17] Update example project. --- .../Coordinators/CustomShapes/CustomShapesRoute.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/CustomShapes/CustomShapesRoute.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/CustomShapes/CustomShapesRoute.swift index aeb22bb..26fdc56 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/CustomShapes/CustomShapesRoute.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/CustomShapes/CustomShapesRoute.swift @@ -28,11 +28,6 @@ enum CustomShapesRoute: NavigationRoute { } var action: TransitionAction? { - switch self { - case .tower: - return .present(animated: true, modalPresentationStyle: .fullScreen) - default: - return .push(animated: true) - } + return .push(animated: true) } } From c4c2219e156901e71d5ab3156c19f5b39e534ca4 Mon Sep 17 00:00:00 2001 From: Erik Drobne Date: Mon, 15 May 2023 17:21:31 +0200 Subject: [PATCH 10/17] Update readme. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a2a5446..e32ae57 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ class ShapesCoordinator: NSObject, Coordinator, Navigator { // MARK: - Internal properties /// Root coordinator doesn't have a parent. - weak var parent: Coordinator? = nil + let parent: Coordinator? = nil var childCoordinators = [Coordinator]() var navigationController: UINavigationController let startRoute: ShapesRoute? From 7f558328cdcc48f0b61b9c939b606bf68bcc4d6b Mon Sep 17 00:00:00 2001 From: Erik Drobne Date: Tue, 16 May 2023 15:12:56 +0200 Subject: [PATCH 11/17] Address pr comments. --- README.md | 6 +++++- .../Coordinator/Coordinator.swift | 1 - .../SwiftUICoordinator/Navigator/Navigator.swift | 1 - .../Navigator/NavigatorError.swift | 2 -- .../Routing/NavigationController.swift | 14 +++++--------- .../Routing/NavigationRoute.swift | 2 -- .../Routing/RouteHostingController.swift | 1 - .../SwiftUICoordinator/Routing/RouteProvider.swift | 2 -- .../Routing/RouterViewFactory.swift | 1 - .../SwiftUICoordinator/Transition/Transition.swift | 1 - .../Transition/TransitionAction.swift | 1 - Sources/SwiftUICoordinator/Utilities.swift | 1 - 12 files changed, 10 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index e32ae57..0f68e3e 100644 --- a/README.md +++ b/README.md @@ -280,15 +280,19 @@ import SwiftUICoordinator struct ShapesView: View { @EnvironmentObject var coordinator: Coordinator + @StateObject var viewModel = ViewModel() var body: some View { List { Button { - coordinator?.didTap(route: .simpleShapes) + viewModel.didTapBuiltIn() } label: { Text("Built-in") } } + .onAppear { + viewModel.coordinator = coordinator + } } } ``` diff --git a/Sources/SwiftUICoordinator/Coordinator/Coordinator.swift b/Sources/SwiftUICoordinator/Coordinator/Coordinator.swift index 50c326f..bf12338 100644 --- a/Sources/SwiftUICoordinator/Coordinator/Coordinator.swift +++ b/Sources/SwiftUICoordinator/Coordinator/Coordinator.swift @@ -5,7 +5,6 @@ // Created by Erik Drobne on 12/12/2022. // -import Foundation import SwiftUI @MainActor diff --git a/Sources/SwiftUICoordinator/Navigator/Navigator.swift b/Sources/SwiftUICoordinator/Navigator/Navigator.swift index b4c6120..1c38d98 100644 --- a/Sources/SwiftUICoordinator/Navigator/Navigator.swift +++ b/Sources/SwiftUICoordinator/Navigator/Navigator.swift @@ -5,7 +5,6 @@ // Created by Erik Drobne on 12/12/2022. // -import Foundation import SwiftUI public typealias Routing = Coordinator & Navigator diff --git a/Sources/SwiftUICoordinator/Navigator/NavigatorError.swift b/Sources/SwiftUICoordinator/Navigator/NavigatorError.swift index 4e05355..0b8b9b1 100644 --- a/Sources/SwiftUICoordinator/Navigator/NavigatorError.swift +++ b/Sources/SwiftUICoordinator/Navigator/NavigatorError.swift @@ -5,8 +5,6 @@ // Created by Erik Drobne on 30/04/2023. // -import Foundation - public enum NavigatorError: Error { case cannotShow(NavigationRoute) } diff --git a/Sources/SwiftUICoordinator/Routing/NavigationController.swift b/Sources/SwiftUICoordinator/Routing/NavigationController.swift index 0dc6b90..a0817f7 100644 --- a/Sources/SwiftUICoordinator/Routing/NavigationController.swift +++ b/Sources/SwiftUICoordinator/Routing/NavigationController.swift @@ -5,14 +5,13 @@ // Created by Erik Drobne on 12/05/2023. // -import Foundation import SwiftUI public class NavigationController: UINavigationController { // MARK: - Properties - public private(set) var transitions = [Transition]() + public private(set) var transitions = [ObjectIdentifier: Transition]() // MARK: - Initialization @@ -33,13 +32,10 @@ public class NavigationController: UINavigationController { // MARK: - Public methods public func register(_ transition: Transition) { - guard transitions.first(where: { element in - return type(of: element) == type(of: transition) - }) == nil else { - return + let transitionType = ObjectIdentifier(type(of: transition)) + if transitions[transitionType] == nil { + transitions[transitionType] = transition } - - transitions.append(transition) } } @@ -51,7 +47,7 @@ extension NavigationController: UINavigationControllerDelegate { from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { - for transition in transitions { + for transition in transitions.values { if let from = (fromVC as? RouteProvider)?.route, let to = (toVC as? RouteProvider)?.route { if transition.isEligible(from: from,to: to) { return transition diff --git a/Sources/SwiftUICoordinator/Routing/NavigationRoute.swift b/Sources/SwiftUICoordinator/Routing/NavigationRoute.swift index 8eaf821..4318716 100644 --- a/Sources/SwiftUICoordinator/Routing/NavigationRoute.swift +++ b/Sources/SwiftUICoordinator/Routing/NavigationRoute.swift @@ -5,8 +5,6 @@ // Created by Erik Drobne on 12/12/2022. // -import Foundation - @MainActor public protocol NavigationRoute { /// This title can be used to set the navigation bar title when the route is shown. diff --git a/Sources/SwiftUICoordinator/Routing/RouteHostingController.swift b/Sources/SwiftUICoordinator/Routing/RouteHostingController.swift index ad4ade5..01151a0 100644 --- a/Sources/SwiftUICoordinator/Routing/RouteHostingController.swift +++ b/Sources/SwiftUICoordinator/Routing/RouteHostingController.swift @@ -5,7 +5,6 @@ // Created by Erik Drobne on 15/05/2023. // -import Foundation import SwiftUI public class RouteHostingController: UIHostingController, RouteProvider { diff --git a/Sources/SwiftUICoordinator/Routing/RouteProvider.swift b/Sources/SwiftUICoordinator/Routing/RouteProvider.swift index 4647f9f..f8a896c 100644 --- a/Sources/SwiftUICoordinator/Routing/RouteProvider.swift +++ b/Sources/SwiftUICoordinator/Routing/RouteProvider.swift @@ -5,8 +5,6 @@ // Created by Erik Drobne on 15/05/2023. // -import Foundation - public protocol RouteProvider { var route: NavigationRoute { get } } diff --git a/Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift b/Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift index 154f320..8a5ce71 100644 --- a/Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift +++ b/Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift @@ -5,7 +5,6 @@ // Created by Erik Drobne on 12/12/2022. // -import Foundation import SwiftUI @MainActor diff --git a/Sources/SwiftUICoordinator/Transition/Transition.swift b/Sources/SwiftUICoordinator/Transition/Transition.swift index 572acdc..12a9794 100644 --- a/Sources/SwiftUICoordinator/Transition/Transition.swift +++ b/Sources/SwiftUICoordinator/Transition/Transition.swift @@ -5,7 +5,6 @@ // Created by Erik Drobne on 15/05/2023. // -import Foundation import SwiftUI public protocol Transition: UIViewControllerAnimatedTransitioning { diff --git a/Sources/SwiftUICoordinator/Transition/TransitionAction.swift b/Sources/SwiftUICoordinator/Transition/TransitionAction.swift index e88de36..1adab2d 100644 --- a/Sources/SwiftUICoordinator/Transition/TransitionAction.swift +++ b/Sources/SwiftUICoordinator/Transition/TransitionAction.swift @@ -5,7 +5,6 @@ // Created by Erik Drobne on 12/12/2022. // -import Foundation import SwiftUI public enum TransitionAction { diff --git a/Sources/SwiftUICoordinator/Utilities.swift b/Sources/SwiftUICoordinator/Utilities.swift index 5377914..7effa32 100644 --- a/Sources/SwiftUICoordinator/Utilities.swift +++ b/Sources/SwiftUICoordinator/Utilities.swift @@ -5,7 +5,6 @@ // Created by Erik Drobne on 16/03/2023. // -import Foundation import SwiftUI public extension View { From 5f34567dab33443c51b39c8caa8a4292b888fe9b Mon Sep 17 00:00:00 2001 From: Erik Drobne Date: Tue, 16 May 2023 15:22:39 +0200 Subject: [PATCH 12/17] Add operation function argument to isEligible method. --- .../Transitions/FadeTransition.swift | 2 +- README.md | 2 +- Sources/SwiftUICoordinator/Routing/NavigationController.swift | 2 +- Sources/SwiftUICoordinator/Transition/Transition.swift | 4 +++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Transitions/FadeTransition.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Transitions/FadeTransition.swift index 146ab6d..8488b44 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Transitions/FadeTransition.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Transitions/FadeTransition.swift @@ -9,7 +9,7 @@ import SwiftUI import SwiftUICoordinator class FadeTransition: NSObject, Transition { - func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute) -> Bool { + func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute, operation: NavigationOperation) -> Bool { return (fromRoute as? CustomShapesRoute == .customShapes && toRoute as? CustomShapesRoute == .star) } diff --git a/README.md b/README.md index 0f68e3e..fcaca79 100644 --- a/README.md +++ b/README.md @@ -303,7 +303,7 @@ Create custom transition. ```Swift class FadeTransition: NSObject, Transition { - func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute) -> Bool { + func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute, operation: NavigationOperation) -> Bool { return (fromRoute as? CustomShapesRoute == .customShapes && toRoute as? CustomShapesRoute == .star) } diff --git a/Sources/SwiftUICoordinator/Routing/NavigationController.swift b/Sources/SwiftUICoordinator/Routing/NavigationController.swift index a0817f7..bc0d8f1 100644 --- a/Sources/SwiftUICoordinator/Routing/NavigationController.swift +++ b/Sources/SwiftUICoordinator/Routing/NavigationController.swift @@ -49,7 +49,7 @@ extension NavigationController: UINavigationControllerDelegate { for transition in transitions.values { if let from = (fromVC as? RouteProvider)?.route, let to = (toVC as? RouteProvider)?.route { - if transition.isEligible(from: from,to: to) { + if transition.isEligible(from: from,to: to, operation: operation) { return transition } } diff --git a/Sources/SwiftUICoordinator/Transition/Transition.swift b/Sources/SwiftUICoordinator/Transition/Transition.swift index 12a9794..f4f9ecf 100644 --- a/Sources/SwiftUICoordinator/Transition/Transition.swift +++ b/Sources/SwiftUICoordinator/Transition/Transition.swift @@ -7,6 +7,8 @@ import SwiftUI +public typealias NavigationOperation = UINavigationController.Operation + public protocol Transition: UIViewControllerAnimatedTransitioning { - func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute) -> Bool + func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute, operation: NavigationOperation) -> Bool } From 5d5e5451df6ee5b1c82f3e880abf99a26341257c Mon Sep 17 00:00:00 2001 From: Erik Drobne Date: Tue, 16 May 2023 16:34:18 +0200 Subject: [PATCH 13/17] Remove if let modifier on view creation. --- .../Navigator/Navigator.swift | 11 ++++------- Sources/SwiftUICoordinator/Utilities.swift | 19 ------------------- 2 files changed, 4 insertions(+), 26 deletions(-) delete mode 100644 Sources/SwiftUICoordinator/Utilities.swift diff --git a/Sources/SwiftUICoordinator/Navigator/Navigator.swift b/Sources/SwiftUICoordinator/Navigator/Navigator.swift index 1c38d98..cec062f 100644 --- a/Sources/SwiftUICoordinator/Navigator/Navigator.swift +++ b/Sources/SwiftUICoordinator/Navigator/Navigator.swift @@ -55,10 +55,8 @@ public extension Navigator where Self: Coordinator, Self: RouterViewFactory { func show(route: Route) throws { let view = self.view(for: route) - .ifLet(route.title) { view, value in - view.navigationTitle(value) - } - + .navigationTitle(route.title ?? "") + let viewWithCoordinator = view.environmentObject(self) let viewController = RouteHostingController( rootView: viewWithCoordinator, @@ -104,9 +102,8 @@ public extension Navigator where Self: Coordinator, Self: RouterViewFactory { private func views(for routes: [Route]) -> [UIHostingController] { return routes.map({ route in - let view = self.view(for: route).ifLet(route.title) { view, value in - view.navigationTitle(value) - } + let view = self.view(for: route) + .navigationTitle(route.title ?? "") return RouteHostingController(rootView: view.environmentObject(self), route: route) }) } diff --git a/Sources/SwiftUICoordinator/Utilities.swift b/Sources/SwiftUICoordinator/Utilities.swift deleted file mode 100644 index 7effa32..0000000 --- a/Sources/SwiftUICoordinator/Utilities.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// Utilities.swift -// -// -// Created by Erik Drobne on 16/03/2023. -// - -import SwiftUI - -public extension View { - @ViewBuilder - func ifLet(_ value: T?, modifier: (Self, T) -> Content) -> some View { - if let value = value { - modifier(self, value) - } else { - self - } - } -} From 3612d376bbfdaa544b6186226799a7a1d4518458 Mon Sep 17 00:00:00 2001 From: Erik Drobne Date: Tue, 16 May 2023 16:54:56 +0200 Subject: [PATCH 14/17] Code improvements. --- .../Routing/NavigationController.swift | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/Sources/SwiftUICoordinator/Routing/NavigationController.swift b/Sources/SwiftUICoordinator/Routing/NavigationController.swift index bc0d8f1..5c82174 100644 --- a/Sources/SwiftUICoordinator/Routing/NavigationController.swift +++ b/Sources/SwiftUICoordinator/Routing/NavigationController.swift @@ -9,13 +9,17 @@ import SwiftUI public class NavigationController: UINavigationController { - // MARK: - Properties + // MARK: - Internal Properties - public private(set) var transitions = [ObjectIdentifier: Transition]() + private(set) var transitions = [ObjectIdentifier: Transition]() // MARK: - Initialization - public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + public convenience init() { + self.init(nibName: nil, bundle: nil) + } + + private override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) self.delegate = self @@ -25,10 +29,6 @@ public class NavigationController: UINavigationController { fatalError("init(coder:) has not been implemented") } - public convenience init() { - self.init(nibName: nil, bundle: nil) - } - // MARK: - Public methods public func register(_ transition: Transition) { @@ -48,11 +48,15 @@ extension NavigationController: UINavigationControllerDelegate { to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { for transition in transitions.values { - if let from = (fromVC as? RouteProvider)?.route, let to = (toVC as? RouteProvider)?.route { - if transition.isEligible(from: from,to: to, operation: operation) { - return transition - } + guard + let from = (fromVC as? RouteProvider)?.route, + let to = (toVC as? RouteProvider)?.route, + transition.isEligible(from: from,to: to, operation: operation) + else { + return nil } + + return transition } return nil From 1135463dfb1407afe8b3051e4962beb30ab0a53e Mon Sep 17 00:00:00 2001 From: Erik Drobne Date: Tue, 16 May 2023 16:59:20 +0200 Subject: [PATCH 15/17] Throw an error when starting navigator if route is not set. --- Sources/SwiftUICoordinator/Navigator/Navigator.swift | 2 +- Sources/SwiftUICoordinator/Navigator/NavigatorError.swift | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftUICoordinator/Navigator/Navigator.swift b/Sources/SwiftUICoordinator/Navigator/Navigator.swift index cec062f..1cf7ff3 100644 --- a/Sources/SwiftUICoordinator/Navigator/Navigator.swift +++ b/Sources/SwiftUICoordinator/Navigator/Navigator.swift @@ -47,7 +47,7 @@ public extension Navigator where Self: Coordinator, Self: RouterViewFactory { func start() throws { guard let route = startRoute else { - return + throw NavigatorError.startRouteMissing } try show(route: route) diff --git a/Sources/SwiftUICoordinator/Navigator/NavigatorError.swift b/Sources/SwiftUICoordinator/Navigator/NavigatorError.swift index 0b8b9b1..7d29fc5 100644 --- a/Sources/SwiftUICoordinator/Navigator/NavigatorError.swift +++ b/Sources/SwiftUICoordinator/Navigator/NavigatorError.swift @@ -6,5 +6,6 @@ // public enum NavigatorError: Error { + case startRouteMissing case cannotShow(NavigationRoute) } From 5bad13258489209b908c69fa2ff9c0548fdcaed7 Mon Sep 17 00:00:00 2001 From: Erik Drobne Date: Tue, 16 May 2023 17:26:58 +0200 Subject: [PATCH 16/17] Update readme. --- .../Transitions/FadeTransition.swift | 2 +- README.md | 22 +++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Transitions/FadeTransition.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Transitions/FadeTransition.swift index 8488b44..d0bbd07 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Transitions/FadeTransition.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Transitions/FadeTransition.swift @@ -14,7 +14,7 @@ class FadeTransition: NSObject, Transition { } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - return 0.3 // Set the duration of the fade animation + return 0.3 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { diff --git a/README.md b/README.md index fcaca79..6c66f92 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ This protocol defines the available routes for navigation within a coordinator f ```Swift public protocol NavigationRoute { - /// This title can be used to set the navigation bar title when the route is shown. + /// Navigation bar title. var title: String? { get } /// Transition action to be used when the route is shown. /// This can be a push action, a modal presentation, or `nil` (for child coordinators). @@ -71,7 +71,7 @@ public typealias Routing = Coordinator & Navigator public protocol Navigator: ObservableObject { associatedtype Route: NavigationRoute - var navigationController: UINavigationController { get set } + var navigationController: NavigationController { get set } /// The starting route of the navigator. var startRoute: Route? { get } @@ -154,7 +154,7 @@ enum ShapesRoute: NavigationRoute { ### Create Coordinator -Our `ShapesCoordinator` has to conform to the `Navigator` protocol and implement the `navigate(to route: NavigationRoute)` to execute flow-specific logic on method execution. Root coordinator has to initialize `UINavigationController`. +Our `ShapesCoordinator` has to conform to the `Navigator` protocol and implement the `navigate(to route: NavigationRoute)` to execute flow-specific logic on method execution. Root coordinator has to initialize `NavigationController`. ```Swift class ShapesCoordinator: NSObject, Coordinator, Navigator { @@ -164,15 +164,17 @@ class ShapesCoordinator: NSObject, Coordinator, Navigator { /// Root coordinator doesn't have a parent. let parent: Coordinator? = nil var childCoordinators = [Coordinator]() - var navigationController: UINavigationController + var navigationController: NavigationController let startRoute: ShapesRoute? // MARK: - Initialization - init(navigationController: UINavigationController = .init(), startRoute: ShapesRoute? = nil) { - self.navigationController = navigationController + init(startRoute: ShapesRoute? = nil) { + self.navigationController = NavigationController() self.startRoute = startRoute super.init() + + setup() } func navigate(to route: NavigationRoute) { @@ -198,6 +200,12 @@ class ShapesCoordinator: NSObject, Coordinator, Navigator { return } } + + // MARK: - Private methods + + private func setup() { + navigationController.register(FadeTransition()) + } } ``` @@ -299,7 +307,7 @@ struct ShapesView: View { ### Custom transitions -Create custom transition. +Create custom Fade transition. ```Swift class FadeTransition: NSObject, Transition { From d99bcd67b23c13aec939890a5fd5d88e5b56bbe7 Mon Sep 17 00:00:00 2001 From: Erik Drobne Date: Mon, 22 May 2023 10:49:47 +0200 Subject: [PATCH 17/17] Return conditional modifer back to the navigator. --- .../Navigator/Navigator.swift | 4 +++- .../Routing/RouterViewFactory.swift | 9 ++++++++- .../SwiftUICoordinator/View+Utilities.swift | 19 +++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 Sources/SwiftUICoordinator/View+Utilities.swift diff --git a/Sources/SwiftUICoordinator/Navigator/Navigator.swift b/Sources/SwiftUICoordinator/Navigator/Navigator.swift index 1cf7ff3..0429c1e 100644 --- a/Sources/SwiftUICoordinator/Navigator/Navigator.swift +++ b/Sources/SwiftUICoordinator/Navigator/Navigator.swift @@ -55,7 +55,9 @@ public extension Navigator where Self: Coordinator, Self: RouterViewFactory { func show(route: Route) throws { let view = self.view(for: route) - .navigationTitle(route.title ?? "") + .ifLet(route.title) { view, value in + view.navigationTitle(value) + } let viewWithCoordinator = view.environmentObject(self) let viewController = RouteHostingController( diff --git a/Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift b/Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift index 8a5ce71..aabffa3 100644 --- a/Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift +++ b/Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift @@ -11,7 +11,14 @@ import SwiftUI public protocol RouterViewFactory { associatedtype V: View associatedtype Route: NavigationRoute - + @ViewBuilder func view(for route: Route) -> V } + + + + + + + diff --git a/Sources/SwiftUICoordinator/View+Utilities.swift b/Sources/SwiftUICoordinator/View+Utilities.swift new file mode 100644 index 0000000..1fbb692 --- /dev/null +++ b/Sources/SwiftUICoordinator/View+Utilities.swift @@ -0,0 +1,19 @@ +// +// View+Utilities.swift +// +// +// Created by Erik Drobne on 22/05/2023. +// + +import SwiftUI + +public extension View { + @ViewBuilder + func ifLet(_ value: T?, modifier: (Self, T) -> Content) -> some View { + if let value = value { + modifier(self, value) + } else { + self + } + } +}