From 2a87d7c1a0198059e168326c1e00373e8d069de4 Mon Sep 17 00:00:00 2001 From: Erik Drobne Date: Wed, 19 Jun 2024 22:55:51 +0200 Subject: [PATCH] feature(tab bar coordinator): add support for transition action when starting the flow --- .../Coordinators/Tabs/TabsCoordinator.swift | 18 +++++- .../Navigator/Navigator.swift | 59 +------------------ .../Routing/RouterViewFactory.swift | 21 +++++++ .../TabBarCoordinator/TabBarCoordinator.swift | 53 +++++++++++++++++ 4 files changed, 90 insertions(+), 61 deletions(-) create mode 100644 Sources/SwiftUICoordinator/TabBarCoordinator/TabBarCoordinator.swift diff --git a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Tabs/TabsCoordinator.swift b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Tabs/TabsCoordinator.swift index bc397f8..2a4383d 100644 --- a/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Tabs/TabsCoordinator.swift +++ b/Example/SwiftUICoordinatorExample/SwiftUICoordinatorExample/Coordinators/Tabs/TabsCoordinator.swift @@ -41,11 +41,23 @@ extension TabsCoordinator: RouterViewFactory { public func view(for route: TabsCoordinatorRoute) -> some View { switch route { case .red: - Text("Red") + VStack { + Circle() + .foregroundStyle(.red) + } + .padding(16) case .green: - Text("Green") + VStack { + Circle() + .foregroundStyle(.green) + } + .padding(16) case .blue: - Text("Blue") + VStack { + Circle() + .foregroundStyle(.blue) + } + .padding(16) } } } diff --git a/Sources/SwiftUICoordinator/Navigator/Navigator.swift b/Sources/SwiftUICoordinator/Navigator/Navigator.swift index 006ee11..a4fc902 100644 --- a/Sources/SwiftUICoordinator/Navigator/Navigator.swift +++ b/Sources/SwiftUICoordinator/Navigator/Navigator.swift @@ -18,7 +18,7 @@ public protocol Navigator: ObservableObject { /// The starting route of the navigator. var startRoute: Route { get } - /// This method should be called to start the flow and to show the view for the `startRoute`. + /// This method should be called to start the flow and to show the view for the `startRoute`. func start() throws /// It creates a view for the route and adds it to the navigation stack. func show(route: Route) throws @@ -106,25 +106,6 @@ public extension Navigator where Self: RouterViewFactory { } // MARK: - Private methods - - private func hostingController(for route: Route) -> UIHostingController { - let view: some View = self.view(for: route) - .ifLet(route.title) { view, value in - view.navigationTitle(value) - } - .if(route.attachCoordinator) { view in - view.environmentObject(self) - } - - return RouteHostingController( - rootView: view, - route: route - ) - } - - private func views(for routes: [Route]) -> [UIHostingController] { - return routes.map { self.hostingController(for: $0) } - } private func present( viewController: UIViewController, @@ -143,41 +124,3 @@ public extension Navigator where Self: RouterViewFactory { navigationController.present(viewController, animated: animated, completion: completion) } } - -public typealias TabBarRouting = Coordinator & TabBarCoordinatable - -@MainActor -public protocol TabBarCoordinatable: ObservableObject { - associatedtype Route: TabBarNavigationRoute - - var navigationController: NavigationController { get } - var tabBarController: UITabBarController { get } - var tabs: [Route] { get } - func start() -} - -public extension TabBarCoordinatable where Self: RouterViewFactory { - func start() { - tabBarController.viewControllers = views(for: tabs) - navigationController.pushViewController(tabBarController, animated: true) - } - - private func views(for routes: [Route]) -> [UIHostingController] { - return routes.map { self.hostingController(for: $0) } - } - - private func hostingController(for route: Route) -> UIHostingController { - let view: some View = self.view(for: route) - .ifLet(route.title) { view, value in - view.navigationTitle(value) - } - .if(route.attachCoordinator) { view in - view.environmentObject(self) - } - - return RouteHostingController( - rootView: view, - route: route - ) - } -} diff --git a/Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift b/Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift index bd1ff24..7d8475e 100644 --- a/Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift +++ b/Sources/SwiftUICoordinator/Routing/RouterViewFactory.swift @@ -21,3 +21,24 @@ public protocol RouterViewFactory { @ViewBuilder func view(for route: Route) -> V } + +extension RouterViewFactory where Self: ObservableObject { + func hostingController(for route: Route) -> UIHostingController { + let view: some View = self.view(for: route) + .ifLet(route.title) { view, value in + view.navigationTitle(value) + } + .if(route.attachCoordinator) { view in + view.environmentObject(self) + } + + return RouteHostingController( + rootView: view, + route: route + ) + } + + func views(for routes: [Route]) -> [UIHostingController] { + return routes.map { self.hostingController(for: $0) } + } +} diff --git a/Sources/SwiftUICoordinator/TabBarCoordinator/TabBarCoordinator.swift b/Sources/SwiftUICoordinator/TabBarCoordinator/TabBarCoordinator.swift new file mode 100644 index 0000000..6c46b22 --- /dev/null +++ b/Sources/SwiftUICoordinator/TabBarCoordinator/TabBarCoordinator.swift @@ -0,0 +1,53 @@ +import SwiftUI + +public typealias TabBarRouting = Coordinator & TabBarCoordinator + +/// A protocol for managing a tab bar interface in an application. +@MainActor +public protocol TabBarCoordinator: ObservableObject { + associatedtype Route: TabBarNavigationRoute + + var navigationController: NavigationController { get } + var tabBarController: UITabBarController { get } + var tabs: [Route] { get } + /// This method should be called to show the `tabBarController` + func start(with action: TransitionAction) +} + +public extension TabBarCoordinator where Self: RouterViewFactory { + func start(with action: TransitionAction = .push(animated: true)) { + tabBarController.viewControllers = views(for: tabs) + + switch action { + case .push(let animated): + navigationController.pushViewController(tabBarController, animated: animated) + case .present(let animated, let modalPresentationStyle, let delegate, let completion): + present( + viewController: tabBarController, + animated: animated, + modalPresentationStyle: modalPresentationStyle, + delegate: delegate, + completion: completion + ) + } + } + + // MARK: - Private methods + + private func present( + viewController: UITabBarController, + animated: Bool, + modalPresentationStyle: UIModalPresentationStyle, + delegate: UIViewControllerTransitioningDelegate?, + completion: (() -> Void)? + ) { + if let delegate { + viewController.modalPresentationStyle = .custom + viewController.transitioningDelegate = delegate + } else { + viewController.modalPresentationStyle = modalPresentationStyle + } + + navigationController.present(viewController, animated: animated, completion: completion) + } +}