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/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..26fdc56 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? { - return .push() + var action: TransitionAction? { + return .push(animated: true) } } diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesCoordinator.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesCoordinator.swift index 819ac09..0fa6fc8 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesCoordinator.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesCoordinator.swift @@ -13,17 +13,19 @@ 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 - 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.register(FadeTransition()) + } private func makeSimpleShapesCoordinator() -> SimpleShapesCoordinator { let coordinator = SimpleShapesCoordinator(parent: self, navigationController: navigationController) diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesRoute.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesRoute.swift index c3d687f..714d7e6 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesRoute.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Shapes/ShapesRoute.swift @@ -23,13 +23,13 @@ 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. 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..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? { - return .push() + var action: TransitionAction? { + 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..d0bbd07 --- /dev/null +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Transitions/FadeTransition.swift @@ -0,0 +1,37 @@ +// +// 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, operation: NavigationOperation) -> Bool { + return (fromRoute as? CustomShapesRoute == .customShapes && toRoute as? CustomShapesRoute == .star) + } + + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return 0.3 + } + + 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/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/README.md b/README.md index 70b8278..6c66f92 100644 --- a/README.md +++ b/README.md @@ -49,11 +49,11 @@ 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 } - /// 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 } } ``` @@ -71,14 +71,14 @@ 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 } /// 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. @@ -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 { @@ -162,17 +162,19 @@ 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 - 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()) + } } ``` @@ -280,19 +288,69 @@ 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 + } } } ``` +### Custom transitions + +Create custom Fade transition. + +```Swift +class FadeTransition: NSObject, Transition { + func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute, operation: NavigationOperation) -> 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. diff --git a/Sources/SwiftUICoordinator/Coordinator.swift b/Sources/SwiftUICoordinator/Coordinator/Coordinator.swift similarity index 90% rename from Sources/SwiftUICoordinator/Coordinator.swift rename to Sources/SwiftUICoordinator/Coordinator/Coordinator.swift index 70acc64..bf12338 100644 --- a/Sources/SwiftUICoordinator/Coordinator.swift +++ b/Sources/SwiftUICoordinator/Coordinator/Coordinator.swift @@ -17,7 +17,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.swift b/Sources/SwiftUICoordinator/Navigator/Navigator.swift similarity index 84% rename from Sources/SwiftUICoordinator/Navigator.swift rename to Sources/SwiftUICoordinator/Navigator/Navigator.swift index 0483ea6..0429c1e 100644 --- a/Sources/SwiftUICoordinator/Navigator.swift +++ b/Sources/SwiftUICoordinator/Navigator/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 @@ -25,7 +25,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,10 +42,12 @@ 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 { - return + throw NavigatorError.startRouteMissing } try show(route: route) @@ -52,11 +58,14 @@ public extension Navigator where Self: Coordinator, Self: RouterViewFactory { .ifLet(route.title) { view, value in view.navigationTitle(value) } - + let viewWithCoordinator = view.environmentObject(self) - let viewController = UIHostingController(rootView: viewWithCoordinator) + let viewController = RouteHostingController( + rootView: viewWithCoordinator, + 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): @@ -95,10 +104,9 @@ 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) - } - return UIHostingController(rootView: view.environmentObject(self)) + let view = self.view(for: route) + .navigationTitle(route.title ?? "") + return RouteHostingController(rootView: view.environmentObject(self), route: route) }) } diff --git a/Sources/SwiftUICoordinator/NavigatorError.swift b/Sources/SwiftUICoordinator/Navigator/NavigatorError.swift similarity index 85% rename from Sources/SwiftUICoordinator/NavigatorError.swift rename to Sources/SwiftUICoordinator/Navigator/NavigatorError.swift index 4e05355..7d29fc5 100644 --- a/Sources/SwiftUICoordinator/NavigatorError.swift +++ b/Sources/SwiftUICoordinator/Navigator/NavigatorError.swift @@ -5,8 +5,7 @@ // Created by Erik Drobne on 30/04/2023. // -import Foundation - public enum NavigatorError: Error { + case startRouteMissing case cannotShow(NavigationRoute) } diff --git a/Sources/SwiftUICoordinator/Routing/NavigationController.swift b/Sources/SwiftUICoordinator/Routing/NavigationController.swift new file mode 100644 index 0000000..5c82174 --- /dev/null +++ b/Sources/SwiftUICoordinator/Routing/NavigationController.swift @@ -0,0 +1,64 @@ +// +// NavigationController.swift +// +// +// Created by Erik Drobne on 12/05/2023. +// + +import SwiftUI + +public class NavigationController: UINavigationController { + + // MARK: - Internal Properties + + private(set) var transitions = [ObjectIdentifier: Transition]() + + // MARK: - Initialization + + 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 + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Public methods + + public func register(_ transition: Transition) { + let transitionType = ObjectIdentifier(type(of: transition)) + if transitions[transitionType] == nil { + transitions[transitionType] = transition + } + } +} + +// MARK: - UINavigationControllerDelegate + +extension NavigationController: UINavigationControllerDelegate { + public func navigationController(_ navigationController: UINavigationController, + animationControllerFor operation: UINavigationController.Operation, + from fromVC: UIViewController, + to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { + + for transition in transitions.values { + 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 + } +} diff --git a/Sources/SwiftUICoordinator/NavigationRoute.swift b/Sources/SwiftUICoordinator/Routing/NavigationRoute.swift similarity index 51% rename from Sources/SwiftUICoordinator/NavigationRoute.swift rename to Sources/SwiftUICoordinator/Routing/NavigationRoute.swift index ab832c4..4318716 100644 --- a/Sources/SwiftUICoordinator/NavigationRoute.swift +++ b/Sources/SwiftUICoordinator/Routing/NavigationRoute.swift @@ -5,13 +5,11 @@ // 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. 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/Routing/RouteHostingController.swift b/Sources/SwiftUICoordinator/Routing/RouteHostingController.swift new file mode 100644 index 0000000..01151a0 --- /dev/null +++ b/Sources/SwiftUICoordinator/Routing/RouteHostingController.swift @@ -0,0 +1,26 @@ +// +// RouteHostingController.swift +// +// +// Created by Erik Drobne on 15/05/2023. +// + +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) + } + + @objc required dynamic init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Sources/SwiftUICoordinator/Routing/RouteProvider.swift b/Sources/SwiftUICoordinator/Routing/RouteProvider.swift new file mode 100644 index 0000000..f8a896c --- /dev/null +++ b/Sources/SwiftUICoordinator/Routing/RouteProvider.swift @@ -0,0 +1,10 @@ +// +// RouteProvider.swift +// +// +// Created by Erik Drobne on 15/05/2023. +// + +public protocol RouteProvider { + var route: NavigationRoute { get } +} diff --git a/Sources/SwiftUICoordinator/RouterViewFactory.swift b/Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift similarity index 96% rename from Sources/SwiftUICoordinator/RouterViewFactory.swift rename to Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift index 8a5ce71..aabffa3 100644 --- a/Sources/SwiftUICoordinator/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/Transition/Transition.swift b/Sources/SwiftUICoordinator/Transition/Transition.swift new file mode 100644 index 0000000..f4f9ecf --- /dev/null +++ b/Sources/SwiftUICoordinator/Transition/Transition.swift @@ -0,0 +1,14 @@ +// +// Transition.swift +// +// +// Created by Erik Drobne on 15/05/2023. +// + +import SwiftUI + +public typealias NavigationOperation = UINavigationController.Operation + +public protocol Transition: UIViewControllerAnimatedTransitioning { + func isEligible(from fromRoute: NavigationRoute, to toRoute: NavigationRoute, operation: NavigationOperation) -> Bool +} diff --git a/Sources/SwiftUICoordinator/NavigationTransition.swift b/Sources/SwiftUICoordinator/Transition/TransitionAction.swift similarity index 58% rename from Sources/SwiftUICoordinator/NavigationTransition.swift rename to Sources/SwiftUICoordinator/Transition/TransitionAction.swift index a30d1a1..1adab2d 100644 --- a/Sources/SwiftUICoordinator/NavigationTransition.swift +++ b/Sources/SwiftUICoordinator/Transition/TransitionAction.swift @@ -1,17 +1,16 @@ // -// NavigationTransition.swift +// TransitionAction.swift // // // Created by Erik Drobne on 12/12/2022. // -import Foundation import SwiftUI -public enum NavigationTransition { - case push(animated: Bool = true) +public enum TransitionAction { + case push(animated: Bool) case present( - animated: Bool = true, + animated: Bool, modalPresentationStyle: UIModalPresentationStyle = .automatic, completion: (() -> Void)? = nil ) diff --git a/Sources/SwiftUICoordinator/Utilities.swift b/Sources/SwiftUICoordinator/View+Utilities.swift similarity index 80% rename from Sources/SwiftUICoordinator/Utilities.swift rename to Sources/SwiftUICoordinator/View+Utilities.swift index 7effa32..1fbb692 100644 --- a/Sources/SwiftUICoordinator/Utilities.swift +++ b/Sources/SwiftUICoordinator/View+Utilities.swift @@ -1,8 +1,8 @@ // -// Utilities.swift +// View+Utilities.swift // // -// Created by Erik Drobne on 16/03/2023. +// Created by Erik Drobne on 22/05/2023. // import SwiftUI