Created
April 24, 2025 13:07
-
-
Save mdb1/9eb7c3ee9bcad89fb0a83159b9c17b44 to your computer and use it in GitHub Desktop.
Revisions
-
mdb1 created this gist
Apr 24, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,92 @@ import Observation import SwiftUI /// A generic navigation router that manages a stack-based navigation. /// /// `NavigationRouter` is an observable object that tracks a navigation stack using an array of routes. /// It provides methods to push, pop, and reset navigation state, allowing for simple navigation flows. /// /// - Note: The `Route` type must conform to `Hashable`. /// - Note: You can see a visual representation of the stack using the `pathDebugDescription` property. /// /// Usage /// =================================== /// 1. Create an `enum` with the possible navigation destinations. /// 2. Create a `@State` property in the `Router` (or first screen of the flow) for the `NavigationRouter<Route>`. /// 3. Add a `NavigationStack(path: $router.navigationPath)`. /// 4. Add a `navigationDestination` modifier for the Route enum. /// 5. Use the `.environment` modifier to share the `Router` with the child screens. /// 6. In the child screens, add `@Environment(NavigationRouter<Route>.self) var router` to use the router /// /// Example /// =================================== /// ```swift /// struct SampleFlowScreen: View { /// @State private var router = NavigationRouter<Route>() // Define the Router /// /// var body: some View { /// NavigationStack(path: $router.navigationPath) { /// SampleFlowHomeScreen() /// .navigationDestination(for: Route.self) { destination in /// switch destination { /// case .stepA: /// ScreenA() /// case .stepB: /// ScreenB() /// } /// } /// } /// .environment(router) // Share the router with the children screens/views. /// } /// } /// /// extension SampleFlowScreen { /// enum Route: Hashable { /// case stepA, stepB // Define the possible destinations /// } /// } /// ``` @Observable final class NavigationRouter<Route: Hashable> { /// The navigation path is the property used in the NavigationStack native component. /// We use this array of routes to determine the stack of screens. var navigationPath: [Route] = [] /// Pushes the given route onto the navigationPath. func push(_ route: Route) { navigationPath.append(route) } /// Removes the last item from the navigationPath. func pop() { guard !navigationPath.isEmpty else { return } navigationPath.removeLast() } /// Removes everything from the navigationPath. func popToRoot() { navigationPath = [] } /// Pops all the elements of the navigationPath up until it finds the given route. /// If the given route is not on the array, it is a no-op (nothing happens). /// It's recommended to disable or enable the button with this action by checking first if the array contains the element. /// Example: /// ```swift /// Button("Back to step 1") { /// router.pop(to: .step1) /// } /// .disabled(!router.navigationPath.contains(.step1)) func pop(to route: Route) { guard let index = navigationPath.firstIndex(of: route) else { return } navigationPath = Array(navigationPath.prefix(through: index)) } /// A string representation of the current navigation stack for debugging purposes. /// /// Example output: `Navigation Path: [home > detail > settings]` var pathDebugDescription: String { let pathDescription = navigationPath.map { String(describing: $0) }.joined(separator: " > ") return "Navigation Path: [\(pathDescription)]" } }