diff --git a/Assets/RoutingBannerArt.png b/Assets/RoutingBannerArt.png deleted file mode 100644 index aacdaaa..0000000 Binary files a/Assets/RoutingBannerArt.png and /dev/null differ diff --git a/ExampleApp/ContentView.swift b/ExampleApp/ContentView.swift index c76a26e..460c746 100644 --- a/ExampleApp/ContentView.swift +++ b/ExampleApp/ContentView.swift @@ -1,21 +1,21 @@ // // ContentView.swift -// Routing +// SwiftUI-Navigation // // Created by James Sedlacek on 5/7/25. // -import Routing +import Navigation import SwiftUI @MainActor public struct ContentView: View { - @Router private var router: [TestRoute] = [] + @DestinationState private var destinations: [TestDestination] = [] public init() {} public var body: some View { - RoutingView(path: $router) { + Navigator(path: $destinations) { VStack { Button("Push Screen", action: pushScreenAction) } @@ -24,7 +24,7 @@ public struct ContentView: View { @MainActor private func pushScreenAction() { - router.navigate(to: .example("Hello World!")) + destinations.navigate(to: .example("Hello World!")) } } diff --git a/ExampleApp/ExampleApp.swift b/ExampleApp/ExampleApp.swift index da70011..7eb423e 100644 --- a/ExampleApp/ExampleApp.swift +++ b/ExampleApp/ExampleApp.swift @@ -1,6 +1,6 @@ // // ExampleApp.swift -// Routing +// SwiftUI-Navigation // // Created by James Sedlacek on 5/7/25. // diff --git a/ExampleApp/ExampleView.swift b/ExampleApp/ExampleView.swift index 20ca0ea..35b5f83 100644 --- a/ExampleApp/ExampleView.swift +++ b/ExampleApp/ExampleView.swift @@ -1,9 +1,9 @@ -import Routing +import Navigation import SwiftUI public struct ExampleView: View { - @Router private var router: [TestRoute] = [] - @State private var sheetRoute: SheetRoute? = nil + @DestinationState private var destinations: [TestDestination] = [] + @State private var sheetDestination: SheetDestination? = nil private let title: String @MainActor @@ -19,21 +19,21 @@ public struct ExampleView: View { Button("Present Sheet", action: presentSheetAction) } - .sheet(item: $sheetRoute) + .sheet(item: $sheetDestination) } @MainActor private func pushScreenAction() { - router.navigate(to: .lastExample) + destinations.navigate(to: .lastExample) } private func presentSheetAction() { - sheetRoute = .sheetExample("It's a whole new world!") + sheetDestination = .sheetExample("It's a whole new world!") } } public struct SheetExampleView: View { - @State private var route: AnotherRoute? = nil + @State private var destination: AnotherDestination? = nil private let title: String public init(title: String) { @@ -47,12 +47,12 @@ public struct SheetExampleView: View { Text(title) } - .navigationDestination(item: $route) + .navigationDestination(item: $destination) } } private func pushScreenAction() { - route = .anotherExample("Testing") + destination = .anotherExample("Testing") } } @@ -69,7 +69,7 @@ public struct AnotherExampleView: View { } public struct LastExampleView: View { - @Router private var router: [TestRoute] = [] + @DestinationState private var destinations: [TestDestination] = [] @MainActor public init() {} @@ -80,6 +80,6 @@ public struct LastExampleView: View { @MainActor private func navigateToRootAction() { - router.navigateToRoot() + destinations.navigateToRoot() } } diff --git a/ExampleApp/TestRoute.swift b/ExampleApp/TestDestination.swift similarity index 75% rename from ExampleApp/TestRoute.swift rename to ExampleApp/TestDestination.swift index 36cd5ec..36fbcba 100644 --- a/ExampleApp/TestRoute.swift +++ b/ExampleApp/TestDestination.swift @@ -1,14 +1,14 @@ // -// TestRoute.swift -// Routing +// TestDestination.swift +// SwiftUI-Navigation // // Created by James Sedlacek on 5/7/25. // -import Routing +import Navigation import SwiftUI -public enum TestRoute: Routable { +public enum TestDestination: Destination { case example(String) case lastExample @@ -22,7 +22,7 @@ public enum TestRoute: Routable { } } -public enum SheetRoute: Routable { +public enum SheetDestination: Destination { case sheetExample(String) public var body: some View { @@ -33,11 +33,11 @@ public enum SheetRoute: Routable { } } -extension SheetRoute: Identifiable { +extension SheetDestination: Identifiable { public nonisolated var id: Self { self } } -public enum AnotherRoute: Routable { +public enum AnotherDestination: Destination { case anotherExample(String) public var body: some View { diff --git a/Package.swift b/Package.swift index 7c2c7cf..1539d82 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,7 @@ import PackageDescription let package = Package( - name: "Routing", + name: "SwiftUI-Navigation", platforms: [ .iOS(.v16), .macOS(.v13), @@ -13,8 +13,8 @@ let package = Package( ], products: [ .library( - name: "Routing", - targets: ["Routing"] + name: "Navigation", + targets: ["Navigation"] ), .executable( name: "ExampleApp", @@ -22,15 +22,15 @@ let package = Package( ) ], targets: [ - .target(name: "Routing"), + .target(name: "Navigation"), .executableTarget( name: "ExampleApp", - dependencies: ["Routing"], + dependencies: ["Navigation"], path: "ExampleApp" ), .testTarget( - name: "RoutingTests", - dependencies: ["Routing"] + name: "SwiftUINavigationTests", + dependencies: ["Navigation"] ), ] ) diff --git a/README.md b/README.md index dd059ce..1143b68 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,15 @@ -

- -

+# SwiftUI-Navigation [![Swift Package Manager](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager) -[![GitHub stars](https://img.shields.io/github/stars/JamesSedlacek/Routing.svg)](https://github.com/JamesSedlacek/Routing/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/JamesSedlacek/Routing.svg?color=blue)](https://github.com/JamesSedlacek/Routing/network) -[![GitHub contributors](https://img.shields.io/github/contributors/JamesSedlacek/Routing.svg?color=blue)](https://github.com/JamesSedlacek/Routing/network) -Pull Requests Badge -Issues Badge +[![GitHub stars](https://img.shields.io/github/stars/JamesSedlacek/SwiftUI-Navigation.svg)](https://github.com/JamesSedlacek/SwiftUI-Navigation/stargazers) +[![GitHub forks](https://img.shields.io/github/forks/JamesSedlacek/SwiftUI-Navigation.svg?color=blue)](https://github.com/JamesSedlacek/SwiftUI-Navigation/network) +[![GitHub contributors](https://img.shields.io/github/contributors/JamesSedlacek/SwiftUI-Navigation.svg?color=blue)](https://github.com/JamesSedlacek/SwiftUI-Navigation/network) +Pull Requests Badge +Issues Badge ## Description -`Routing` is a **lightweight** SwiftUI navigation library. +`SwiftUI-Navigation` is a **lightweight** SwiftUI navigation library. - Leverages 1st-party APIs `NavigationStack` & `NavigationDestination`. - Never be confused about `NavigationLink` or `NavigationPath` again! (You don't need them) - Type-Safe Navigation (better performance than type-erasing). @@ -29,7 +27,6 @@ 4. [Passing Data Example](#passing-data-example) 5. [View Extensions](#view-extensions) 6. [Under the hood](#under-the-hood) -7. [Author](#author)
@@ -46,30 +43,30 @@ ## Installation -You can install `Routing` using the Swift Package Manager. +You can install `SwiftUI-Navigation` using the Swift Package Manager. 1. In Xcode, select `File` > `Add Package Dependencies`.
2. Copy & paste the following into the `Search or Enter Package URL` search bar. ``` -https://github.com/JamesSedlacek/Routing.git +https://github.com/JamesSedlacek/SwiftUI-Navigation.git ```
-3. Xcode will fetch the repository & the `Routing` library will be added to your project. +3. Xcode will fetch the repository & the `SwiftUI-Navigation` library will be added to your project.
## Getting Started -1. Create a `Route` enum that conforms to the `Routable` protocol. +1. Create a `Destination` enum that conforms to the `Destination` protocol. ``` swift -import Routing +import Navigation import SwiftUI -enum ExampleRoute: Routable { +enum ExampleDestination: Destination { case detail case settings @@ -84,26 +81,26 @@ enum ExampleRoute: Routable { } ``` -2. Create a `Router` object and wrap your `RootView` with a `RoutingView`. +2. Create a `DestinationState` object and wrap your `RootView` with a `Navigator`. ``` swift import SwiftUI -import Routing +import Navigation struct ContentView: View { - @Router private var router: [ExampleRoute] = [] + @DestinationState private var destinations: [ExampleDestination] = [] var body: some View { - RoutingView(path: $router) { + Navigator(path: $destinations) { Button("Go to Settings") { - router.navigate(to: .settings) + destinations.navigate(to: .settings) } } } } ``` -3. Handle navigation using the `Router` functions +3. Handle navigation using the `DestinationState` functions ```swift /// Navigate back in the stack by a specified count. @@ -130,10 +127,10 @@ func replace(with destinations: [Destination]) ## Passing Data Example ```swift -import Routing +import Navigation import SwiftUI -enum ContentRoute: Routable { +enum ContentDestination: Destination { case detail(Color) case settings @@ -148,15 +145,15 @@ enum ContentRoute: Routable { } struct ContentView: View { - @Router private var router: [ContentRoute] = [] + @DestinationState private var destinations: [ContentDestination] = [] private let colors: [Color] = [.red, .green, .blue] var body: some View { - RoutingView(path: $router) { + Navigator(path: $destinations) { List(colors, id: \.self) { color in color .onTapGesture { - router.navigate(to: .detail(color)) + destinations.navigate(to: .detail(color)) } } } @@ -180,34 +177,34 @@ struct ColorDetail: View { ## View Extensions -`Routing` provides several `View` extensions to simplify common navigation and presentation patterns when working with `Routable` types. +`SwiftUI-Navigation` provides several `View` extensions to simplify common navigation and presentation patterns when working with `Destination` types. -### `navigationDestination(for: RouteType.self)` +### `navigationDestination(for: DestinationType.self)` -This extension is a convenience wrapper around the standard SwiftUI `navigationDestination(for:destination:)` modifier. It's tailored for use with types conforming to `Routable`, automatically using the `Routable` instance itself as the destination view. +This extension is a convenience wrapper around the standard SwiftUI `navigationDestination(for:destination:)` modifier. It's tailored for use with types conforming to `Destination`, automatically using the `Destination` instance itself as the destination view. ```swift // Usage within a view: -// SomeView().navigationDestination(for: MyRoute.self) -// This is often handled automatically by RoutingView. +// SomeView().navigationDestination(for: MyDestination.self) +// This is often handled automatically by Navigator. ``` -`RoutingView` uses this extension internally to set up navigation for your `Routable` enum. +`Navigator` uses this extension internally to set up navigation for your `Destination` enum. ### `sheet(item:onDismiss:)` -Presents a sheet when a binding to an optional `Routable & Identifiable` item becomes non-nil. The content of the sheet is the `Routable` item itself. +Presents a sheet when a binding to an optional `Destination & Identifiable` item becomes non-nil. The content of the sheet is the `Destination` item itself. -- `item`: A `Binding` to an optional `Routable & Identifiable` item. +- `item`: A `Binding` to an optional `Destination & Identifiable` item. - `onDismiss`: An optional closure executed when the sheet dismisses. -**Note:** The `Routable` type used with this modifier must also conform to `Identifiable`. +**Note:** The `Destination` type used with this modifier must also conform to `Identifiable`. ```swift import SwiftUI -import Routing +import Navigation -// Ensure your Routable enum conforms to Identifiable. +// Ensure your Destination enum conforms to Identifiable. // For enums with associated values, you might need to add an explicit `id`. -enum ModalRoute: Routable, Identifiable { +enum ModalDestination: Destination, Identifiable { case helpPage case userDetails(id: String) @@ -232,7 +229,7 @@ enum ModalRoute: Routable, Identifiable { } struct MyContentView: View { - @State private var sheetItem: ModalRoute? + @State private var sheetItem: ModalDestination? var body: some View { Button("Show Help Sheet") { @@ -254,23 +251,23 @@ struct UserDetailsView: View { Available on iOS 17.0+, macOS 14.0+, tvOS 17.0+, watchOS 10.0+. -Presents a view using `navigationDestination(item:destination:)` when a binding to an optional `Routable` item becomes non-nil. The destination view is the `Routable` item itself. This is useful for modal-style presentations or alternative navigation flows that don't necessarily push onto the main `NavigationStack`. +Presents a view using `navigationDestination(item:destination:)` when a binding to an optional `Destination` item becomes non-nil. The destination view is the `Destination` item itself. This is useful for modal-style presentations or alternative navigation flows that don't necessarily push onto the main `NavigationStack`. -- `item`: A `Binding` to an optional `Routable` item. +- `item`: A `Binding` to an optional `Destination` item. ```swift import SwiftUI -import Routing +import Navigation -// Assuming MyDetailRoute is a Routable enum -// enum MyDetailRoute: Routable { case info, settings ... } +// Assuming MyDetailDestination is a Destination enum +// enum MyDetailDestination: Destination { case info, settings ... } @available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) struct AnotherScreen: View { - @State private var presentedDetail: MyDetailRoute? // MyDetailRoute conforms to Routable + @State private var presentedDetail: MyDetailDestination? // MyDetailDestination conforms to Destination var body: some View { Button("Show Info Modally") { - presentedDetail = .info // Assuming .info is a case in MyDetailRoute + presentedDetail = .info // Assuming .info is a case in MyDetailDestination } .navigationDestination(item: $presentedDetail) } @@ -281,15 +278,11 @@ struct AnotherScreen: View { ## Under the hood -The `RoutingView` essentially wraps your view with a `NavigationStack`. It uses the `navigationDestination(for: RouteType.self)` view extension (detailed in the "View Extensions" section) to automatically handle presenting the views associated with your `Routable` types. +The `Navigator` essentially wraps your view with a `NavigationStack`. It uses the `navigationDestination(for: DestinationType.self)` view extension (detailed in the "View Extensions" section) to automatically handle presenting the views associated with your `Destination` types. ```swift -// Simplified structure of RoutingView's body: -NavigationStack(path: $path) { // $path is your @Router's binding +// Simplified structure of Navigator's body: +NavigationStack(path: $path) { // $path is your @DestinationState's binding rootContent() - .navigationDestination(for: RouteType.self) // Uses the Routable-specific extension + .navigationDestination(for: DestinationType.self) // Uses the Destination-specific extension } ``` - -## Author - -James Sedlacek, find me on [X/Twitter](https://twitter.com/jsedlacekjr) or [LinkedIn](https://www.linkedin.com/in/jamessedlacekjr/) diff --git a/Sources/Routing/Extensions/Array+Truncate.swift b/Sources/Navigation/Models/Array+Truncate.swift similarity index 100% rename from Sources/Routing/Extensions/Array+Truncate.swift rename to Sources/Navigation/Models/Array+Truncate.swift diff --git a/Sources/Routing/Protocols/Routable.swift b/Sources/Navigation/Models/Destination.swift similarity index 67% rename from Sources/Routing/Protocols/Routable.swift rename to Sources/Navigation/Models/Destination.swift index 819ccc3..97049e0 100644 --- a/Sources/Routing/Protocols/Routable.swift +++ b/Sources/Navigation/Models/Destination.swift @@ -1,5 +1,5 @@ // -// Routable.swift +// Destination.swift // // Created by James Sedlacek on 12/14/23. // @@ -8,14 +8,14 @@ import SwiftUI /// A convenience type-alias used throughout the routing system. /// -/// It bundles the three capabilities a “route” typically needs: +/// It bundles the three capabilities a “destination” typically needs: /// • `View` – supplies the screen’s UI -/// • `Hashable` – lets the route live inside navigation paths/sets +/// • `Hashable` – lets the destination live inside navigation paths/sets /// • `Codable` – enables persistence & deep-link restoration /// /// Usage: /// ```swift -///public enum TestRoute: Routable { +///public enum TestDestination: Destination { /// case example(String) /// case lastExample /// @@ -29,4 +29,4 @@ import SwiftUI /// } ///} /// ``` -public typealias Routable = View & Hashable & Codable +public typealias Destination = View & Hashable & Codable diff --git a/Sources/Routing/Extensions/Array+Routing.swift b/Sources/Navigation/Models/DestinationArray+.swift similarity index 96% rename from Sources/Routing/Extensions/Array+Routing.swift rename to Sources/Navigation/Models/DestinationArray+.swift index b48ca03..85e1908 100644 --- a/Sources/Routing/Extensions/Array+Routing.swift +++ b/Sources/Navigation/Models/DestinationArray+.swift @@ -1,5 +1,5 @@ // -// Array+Routing.swift +// DestinationArray+.swift // // Created by James Sedlacek on 12/16/23. // @@ -7,7 +7,7 @@ import SwiftUI @MainActor -public extension Array where Element: Routable { +public extension Array where Element: Destination { /// Navigate back in the navigation stack by a specified number of destinations. /// diff --git a/Sources/Routing/PropertyWrappers/Router.swift b/Sources/Navigation/Models/DestinationState.swift similarity index 61% rename from Sources/Routing/PropertyWrappers/Router.swift rename to Sources/Navigation/Models/DestinationState.swift index 8a26c05..15d3a30 100644 --- a/Sources/Routing/PropertyWrappers/Router.swift +++ b/Sources/Navigation/Models/DestinationState.swift @@ -1,52 +1,52 @@ // -// Router.swift +// DestinationState.swift // // Created by James Sedlacek on 12/15/23. // import SwiftUI -/// A property-wrapper that keeps a stack of `Routable` values in `UserDefaults` +/// A property-wrapper that keeps a stack of `Destination` values in `UserDefaults` /// (via `@AppStorage`) and exposes it to SwiftUI as mutable state. /// -/// The encoded array is stored under the supplied key (default: `"RouterKey"`), +/// The encoded array is stored under the supplied key (default: `"DestinationStateKey"`), /// allowing the navigation stack to survive app launches or scene re-creation. /// /// Basic usage: /// /// ```swift -/// enum AppRoute: String, Routable { +/// enum AppDestination: String, Destination { /// case home, detail, settings /// } /// /// struct ContentView: View { -/// @Router var routes: [AppRoute] = [.home] // stored under "RouterKey" +/// @DestinationState var destinations: [AppDestination] = [.home] // stored under "DestinationStateKey" /// /// var body: some View { /* … */ } /// } /// /// // Custom key / UserDefaults instance -/// @Router("OnboardingRoutes", store: .standard) -/// var onboarding: [OnboardingRoute] = [.welcome] +/// @DestinationState("OnboardingDestinations", store: .standard) +/// var onboarding: [OnboardingDestination] = [.welcome] /// ``` /// /// Access patterns: -/// • Read/Write: `routes.append(.detail)` -/// • Bindable: `$routes` to drive a `NavigationStack` or similar. +/// • Read/Write: `destinations.append(.detail)` +/// • Bindable: `$destinations` to drive a `NavigationStack` or similar. /// /// - Note: Encoding/decoding uses `JSONEncoder` / `JSONDecoder`. If encoding /// fails the `defaultValue` is returned silently. @MainActor @propertyWrapper -public struct Router: DynamicProperty { +public struct DestinationState: DynamicProperty { @AppStorage private var storage: Data private let encoder: JSONEncoder = .init() private let decoder: JSONDecoder = .init() - private let defaultValue: [RouteType] + private let defaultValue: [T] - public var wrappedValue: [RouteType] { + public var wrappedValue: [T] { get { - guard let decoded = try? decoder.decode([RouteType].self, from: storage) else { + guard let decoded = try? decoder.decode([T].self, from: storage) else { return defaultValue } return decoded @@ -57,7 +57,7 @@ public struct Router: DynamicProperty { } } - public var projectedValue: Binding<[RouteType]> { + public var projectedValue: Binding<[T]> { Binding( get: { wrappedValue }, set: { wrappedValue = $0 } @@ -65,8 +65,8 @@ public struct Router: DynamicProperty { } public init( - wrappedValue: [RouteType], - _ key: String = "RouterKey", + wrappedValue: [T], + _ key: String = "DestinationStateKey", store: UserDefaults? = nil ) { defaultValue = wrappedValue diff --git a/Sources/Routing/Views/RoutingView.swift b/Sources/Navigation/Views/Navigator.swift similarity index 55% rename from Sources/Routing/Views/RoutingView.swift rename to Sources/Navigation/Views/Navigator.swift index c49a8cc..f53f436 100644 --- a/Sources/Routing/Views/RoutingView.swift +++ b/Sources/Navigation/Views/Navigator.swift @@ -1,23 +1,23 @@ // -// RoutingView.swift -// Routing +// Navigator.swift +// SwiftUI-Navigation // import SwiftUI /// A thin convenience wrapper around `NavigationStack` that -/// drives navigation from an external `[RouteType]` array. +/// drives navigation from an external `[DestinationType]` array. /// -/// `RoutingView` lets you keep the navigation “path” outside -/// of your view hierarchy (e.g. in `@AppStorage` via `@Router`) +/// `Navigator` lets you keep the navigation “path” outside +/// of your view hierarchy (e.g. in `@AppStorage` via `@DestinationState`) /// while still enjoying type-safe `NavigationStack` behaviour. /// The view creates its own `NavigationStack` under-the-hood /// and forwards a binding to the caller-supplied `path`. /// /// Usage: /// ```swift -/// enum MyRoute: Routable { // 1️⃣ Conform to Routable -/// case profile(Int) // Your route cases +/// enum MyDestination: Destination { // 1️⃣ Conform to Destination +/// case profile(Int) // Your destination cases /// /// var body: some View { // Each case returns a view /// switch self { @@ -28,10 +28,10 @@ import SwiftUI /// } /// /// struct RootView: View { -/// @Router private var path: [MyRoute] = [] // 2️⃣ Persist the path +/// @DestinationState private var path: [MyDestination] = [] // 2️⃣ Persist the path /// /// var body: some View { -/// RoutingView(path: $path) { // 3️⃣ Wrap your root UI +/// Navigator(path: $path) { // 3️⃣ Wrap your root UI /// VStack { /// Button("Show profile") { /// path.navigate(to: .profile(42)) @@ -43,15 +43,15 @@ import SwiftUI /// ``` /// /// - Parameters: -/// - RouteType: The enum/struct you use to describe destinations. -/// Must conform to `Routable`. +/// - DestinationType: The enum/struct you use to describe destinations. +/// Must conform to `Destination`. /// - RootContent: The root view shown at the bottom of the stack. -public struct RoutingView: View { - @Binding private var path: [RouteType] +public struct Navigator: View { + @Binding private var path: [DestinationType] private let rootContent: () -> RootContent public init( - path: Binding<[RouteType]>, + path: Binding<[DestinationType]>, @ViewBuilder rootContent: @escaping () -> RootContent ) { self._path = path @@ -61,7 +61,7 @@ public struct RoutingView: View { public var body: some View { NavigationStack(path: $path) { rootContent() - .navigationDestination(for: RouteType.self) + .navigationDestination(for: DestinationType.self) } } } diff --git a/Sources/Routing/Views/View+Extensions.swift b/Sources/Navigation/Views/View+Extensions.swift similarity index 56% rename from Sources/Routing/Views/View+Extensions.swift rename to Sources/Navigation/Views/View+Extensions.swift index bdcd348..27b83f2 100644 --- a/Sources/Routing/Views/View+Extensions.swift +++ b/Sources/Navigation/Views/View+Extensions.swift @@ -1,6 +1,6 @@ // // View+Extensions.swift -// Routing +// SwiftUI-Navigation // // Created by James Sedlacek on 5/6/25. // @@ -11,43 +11,43 @@ public extension View { /// Associates a destination view with a presented data type for use within a navigation stack. /// /// This is a convenience wrapper around the standard SwiftUI `navigationDestination(for:destination:)` modifier, - /// tailored for use with types conforming to `Routable`. The destination view is the presented `Routable` instance itself. + /// tailored for use with types conforming to `Destination`. The destination view is the presented `Destination` instance itself. /// - /// - Parameter routeType: The type of `Routable` data that this destination binding supports. - /// This should be the metatype (e.g., `MyRoute.self`). - /// - Returns: A view that has a navigation destination associated with the specified `Routable` data type. - func navigationDestination(for routeType: D.Type) -> some View { + /// - Parameter destinationType: The type of `Destination` data that this destination binding supports. + /// This should be the metatype (e.g., `MyDestination.self`). + /// - Returns: A view that has a navigation destination associated with the specified `Destination` data type. + func navigationDestination(for destinationType: D.Type) -> some View { self.navigationDestination(for: D.self, destination: { $0 }) } - /// Presents a sheet when a binding to an optional `Routable` item becomes non-nil. + /// Presents a sheet when a binding to an optional `Destination` item becomes non-nil. /// /// This is a convenience wrapper around the standard SwiftUI `sheet(item:onDismiss:content:)` modifier, - /// tailored for use with types conforming to `Routable`. The content of the sheet is the item itself. + /// tailored for use with types conforming to `Destination`. The content of the sheet is the item itself. /// /// - Parameters: - /// - item: A `Binding` to an optional `Routable` item. When `item` is non-nil, + /// - item: A `Binding` to an optional `Destination` item. When `item` is non-nil, /// the system passes the unwrapped item to the `content` closure, which then provides /// the view for the sheet. If `item` becomes `nil`, the system dismisses the sheet. /// - onDismiss: An optional closure that SwiftUI executes when the sheet dismisses. /// - Returns: A view that presents a sheet when the bound item is non-nil. - func sheet(item: Binding, onDismiss: (() -> Void)? = nil) -> some View { + func sheet(item: Binding, onDismiss: (() -> Void)? = nil) -> some View { self.sheet(item: item, onDismiss: onDismiss, content: { $0 }) } } @available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) public extension View { - /// Presents a view when a binding to an optional `Routable` item becomes non-nil. + /// Presents a view when a binding to an optional `Destination` item becomes non-nil. /// /// This is a convenience wrapper around the standard SwiftUI `navigationDestination(item:destination:)` modifier, - /// tailored for use with types conforming to `Routable`. The destination view is the item itself. + /// tailored for use with types conforming to `Destination`. The destination view is the item itself. /// - /// - Parameter item: A `Binding` to an optional `Routable` item. When `item` is non-nil, + /// - Parameter item: A `Binding` to an optional `Destination` item. When `item` is non-nil, /// the system passes the unwrapped item to the `destination` closure, which then provides /// the view to display. If `item` becomes `nil`, the system dismisses the presented view. /// - Returns: A view that presents another view when the bound item is non-nil. - func navigationDestination(item: Binding) -> some View { + func navigationDestination(item: Binding) -> some View { self.navigationDestination(item: item, destination: { $0 }) } } diff --git a/Tests/RoutingTests/ArrayRoutingTests.swift b/Tests/SwiftUINavigationTests/ArraySwiftUINavigationTests.swift similarity index 75% rename from Tests/RoutingTests/ArrayRoutingTests.swift rename to Tests/SwiftUINavigationTests/ArraySwiftUINavigationTests.swift index 40857a7..d1e733a 100644 --- a/Tests/RoutingTests/ArrayRoutingTests.swift +++ b/Tests/SwiftUINavigationTests/ArraySwiftUINavigationTests.swift @@ -1,17 +1,17 @@ // -// ArrayRoutingTests.swift +// ArraySwiftUINavigationTests.swift // // Created by James Sedlacek on 12/16/23. // import Testing -@testable import Routing +@testable import Navigation @MainActor -struct ArrayRoutingTests { +struct ArraySwiftUINavigationTests { @Test("Navigate to a single destination") func navigateToSingleDestination() { - var stack: [MockRoute] = [] + var stack: [MockDestination] = [] stack.navigate(to: .settings) #expect(stack.count == 1) #expect(stack.last == .settings) @@ -19,7 +19,7 @@ struct ArrayRoutingTests { @Test("Navigate to multiple destinations") func navigateToMultipleDestinations() { - var stack: [MockRoute] = [] + var stack: [MockDestination] = [] stack.navigate(to: [.settings, .profile, .settings]) #expect(stack.count == 3) #expect(stack == [.settings, .profile, .settings]) @@ -27,7 +27,7 @@ struct ArrayRoutingTests { @Test("Navigate back one step") func navigateBackOneStep() { - var stack: [MockRoute] = [.settings, .profile] + var stack: [MockDestination] = [.settings, .profile] stack.navigateBack() #expect(stack.count == 1) #expect(stack == [.settings]) @@ -35,7 +35,7 @@ struct ArrayRoutingTests { @Test("Navigate back zero steps") func navigateBackZeroSteps() { - var stack: [MockRoute] = [.settings, .profile] + var stack: [MockDestination] = [.settings, .profile] stack.navigateBack(0) #expect(stack.count == 2) #expect(stack == [.settings, .profile]) @@ -43,7 +43,7 @@ struct ArrayRoutingTests { @Test("Navigate back negative steps") func navigateBackNegativeSteps() { - var stack: [MockRoute] = [.settings, .profile] + var stack: [MockDestination] = [.settings, .profile] stack.navigateBack(-1) #expect(stack.count == 2) #expect(stack == [.settings, .profile]) @@ -51,7 +51,7 @@ struct ArrayRoutingTests { @Test("Navigate back multiple steps") func navigateBackMultipleSteps() { - var stack: [MockRoute] = [.settings, .profile, .settings, .profile] + var stack: [MockDestination] = [.settings, .profile, .settings, .profile] stack.navigateBack(2) #expect(stack.count == 2) #expect(stack == [.settings, .profile]) @@ -59,21 +59,21 @@ struct ArrayRoutingTests { @Test("Navigate back too many steps") func navigateBackTooManySteps() { - var stack: [MockRoute] = [.settings, .profile] + var stack: [MockDestination] = [.settings, .profile] stack.navigateBack(3) #expect(stack.isEmpty) } @Test("Navigate to root") func navigateToRoot() { - var stack: [MockRoute] = [.settings, .profile, .settings] + var stack: [MockDestination] = [.settings, .profile, .settings] stack.navigateToRoot() #expect(stack.isEmpty) } @Test("Navigate back to a specific destination") func navigateBackToSpecificDestination() { - var stack: [MockRoute] = [.settings, .profile, .settings, .profile, .settings] + var stack: [MockDestination] = [.settings, .profile, .settings, .profile, .settings] stack.navigateBack(to: .profile) // Navigates back to the last occurrence of .profile #expect(stack.count == 4) #expect(stack == [.settings, .profile, .settings, .profile]) @@ -81,7 +81,7 @@ struct ArrayRoutingTests { @Test("Navigate back to a non-existent destination in the stack") func navigateBackToNonExistentDestination() { - var stack: [MockRoute] = [.settings, .settings, .settings] + var stack: [MockDestination] = [.settings, .settings, .settings] // .profile is not in the stack, so it should do nothing. stack.navigateBack(to: .profile) #expect(stack.count == 3) @@ -90,7 +90,7 @@ struct ArrayRoutingTests { @Test("Navigate back to a specific destination that is currently on top") func navigateBackToDestinationOnTop() { - var stack: [MockRoute] = [.settings, .profile] + var stack: [MockDestination] = [.settings, .profile] stack.navigateBack(to: .profile) #expect(stack.count == 2) #expect(stack == [.settings, .profile]) @@ -98,21 +98,21 @@ struct ArrayRoutingTests { @Test("Navigate back to the first occurrence of a destination") func navigateBackToFirstOccurrence() { - var stack: [MockRoute] = [.profile, .settings, .profile, .settings] + var stack: [MockDestination] = [.profile, .settings, .profile, .settings] stack.navigateBack(to: .profile) // Should go to the second .profile #expect(stack == [.profile, .settings, .profile]) } @Test("Replace stack with empty destinations") func replaceWithEmptyDestinations() { - var stack: [MockRoute] = [.settings, .profile, .settings] + var stack: [MockDestination] = [.settings, .profile, .settings] stack.replace(with: []) #expect(stack.isEmpty) } @Test("Replace stack with new destinations") func replaceWithNewDestinations() { - var stack: [MockRoute] = [.settings, .settings] + var stack: [MockDestination] = [.settings, .settings] stack.replace(with: [.profile, .settings, .profile]) #expect(stack.count == 3) #expect(stack == [.profile, .settings, .profile]) diff --git a/Tests/RoutingTests/ArrayTruncationTests.swift b/Tests/SwiftUINavigationTests/ArrayTruncationTests.swift similarity index 97% rename from Tests/RoutingTests/ArrayTruncationTests.swift rename to Tests/SwiftUINavigationTests/ArrayTruncationTests.swift index 04ce284..012500c 100644 --- a/Tests/RoutingTests/ArrayTruncationTests.swift +++ b/Tests/SwiftUINavigationTests/ArrayTruncationTests.swift @@ -6,7 +6,7 @@ // import Testing -@testable import Routing +@testable import Navigation struct ArrayTruncationTests { @Test func truncateToValidIndex() { diff --git a/Tests/RoutingTests/Mocks/MockRoute.swift b/Tests/SwiftUINavigationTests/Mocks/MockDestination.swift similarity index 85% rename from Tests/RoutingTests/Mocks/MockRoute.swift rename to Tests/SwiftUINavigationTests/Mocks/MockDestination.swift index 640ca94..52a680d 100644 --- a/Tests/RoutingTests/Mocks/MockRoute.swift +++ b/Tests/SwiftUINavigationTests/Mocks/MockDestination.swift @@ -1,14 +1,14 @@ // -// MockRoute.swift +// MockDestination.swift // // // Created by James Sedlacek on 2/10/24. // -import Routing +import Navigation import SwiftUI -enum MockRoute: Routable { +enum MockDestination: Destination { case settings case profile