markbattistella/routingmanager
RoutingManager is a SwiftUI navigation package built around a main-actor
Features
- Typed SwiftUI route and stack models
- Observable
NavigationManagerfor push, back, reset, load, and delete operations NavigationWrapperfor binding a manager toNavigationStack- Memory, JSON file, and custom storage support
- Result-based navigation error handling
- Swift 6 language mode
Installation
Add RoutingManager to your Swift project using Swift Package Manager.
dependencies: [
.package(url: "https://github.com/markbattistella/RoutingManager", from: "26.3.8")
]Requirements
- Swift 6.0+
- iOS 17.0+, macOS 14.0+, Mac Catalyst 17.0+, tvOS 17.0+, watchOS 10.0+, visionOS 1.0+
Usage
Define a stack and route type:
import RoutingManager
import SwiftUI
enum AppStack: NavigationStackRepresentable {
case main
}
enum AppRoute: NavigationRouteRepresentable {
case products
case item(id: Int)
@MainActor
@ViewBuilder
var body: some View {
switch self {
case .products:
ProductsView()
case .item(let id):
ItemDetailView(id: id)
}
}
}Wrap your root view in NavigationWrapper:
@main
struct ExampleApp: App {
var body: some Scene {
WindowGroup {
NavigationWrapper(
storage: .memory,
stack: AppStack.main,
for: AppRoute.self
) {
HomeView()
}
}
}
}Access the manager from child views:
struct HomeView: View {
@Environment(NavigationManager<AppStack, AppRoute>.self)
private var navigation
var body: some View {
Button("Products") {
navigation.push(to: .products)
}
}
}Inject dependencies into destinations when needed:
NavigationWrapper(
storage: .json,
stack: AppStack.main,
for: AppRoute.self
) {
HomeView()
} environmentInjection: { route in
route.body
.environment(CatalogService())
}Storage
Memory
Memory storage keeps navigation state only for the lifetime of the manager.
let navigation = NavigationManager<AppStack, AppRoute>(
for: .main,
storageMode: .memory
)JSON
JSON storage writes navigation state to NavigationState.json in the user's documents directory. Call load() when you want to restore a previously saved path.
let navigation = NavigationManager<AppStack, AppRoute>(
for: .main,
storageMode: .json
)
navigation.load()Custom
Implement FileStorageRepresentable when you need a different backing store.
final class XMLFileStorage<T: Codable>: FileStorageRepresentable {
private let fileURL: URL
init(fileName: String = "NavigationState.xml") {
let directory = FileManager.default
.urls(for: .documentDirectory, in: .userDomainMask)
.first ?? URL(fileURLWithPath: NSTemporaryDirectory())
self.fileURL = directory.appendingPathComponent(fileName)
}
func save(_ object: T) throws {
let encoder = PropertyListEncoder()
encoder.outputFormat = .xml
let data = try encoder.encode(object)
try data.write(to: fileURL, options: .atomic)
}
func load() throws -> T? {
guard FileManager.default.fileExists(atPath: fileURL.path) else {
return nil
}
let data = try Data(contentsOf: fileURL)
return try PropertyListDecoder().decode(T.self, from: data)
}
func delete() throws {
guard FileManager.default.fileExists(atPath: fileURL.path) else {
return
}
try FileManager.default.removeItem(at: fileURL)
}
}
let storage = FileStorage(XMLFileStorage<[AppStack: [AppRoute]]>())
let navigation = NavigationManager<AppStack, AppRoute>(
for: .main,
storageMode: .custom(storage)
)License
RoutingManager is released under the MIT license. See LICENSE for details.
[Shield1]: https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fmarkbattistella%2FRoutingManager%2Fbadge%3Ftype%3Dswift-versions
[Shield2]: https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fmarkbattistella%2FRoutingManager%2Fbadge%3Ftype%3Dplatforms
[Shield3]: https://img.shields.io/badge/Licence-MIT-white?labelColor=blue&style=flat
Package Metadata
Repository: markbattistella/routingmanager
Default branch: main
README: README.md