Expert guidance on setting up sRouting and adding new screens/routes in a SwiftUI application.
Defines route enums, configures coordinators, and wires NavigationStack-based navigation for SwiftUI apps using the sRouting framework.
graph TD
subgraph View_Layer
V["SwiftUI View"]
VM["ViewModifier (.onRouting, .routeObserver)"]
end
subgraph Logic_Layer
SRR["SRRouter<Route>"]
SRC["SRContext (Global)"]
end
subgraph State_Layer
COORD["@sRouteCoordinator"]
EMIT["SRCoordinatorEmitter"]
PATH["SRNavigationPath"]
end
V -- "commands" --> SRR
SRR -- "triggers" --> TR["SRTransition"]
VM -- "observes" --> SRR
VM -- "updates" --> SRC
COORD -- "owns" --> EMIT
COORD -- "owns" --> PATH
PATH -- "wraps" --> SWPATH["NavigationPath"]
VM -- "executes" --> SWNAV["Presentation (Sheet, Cover, Alert etc.)"]
VM -- "updates" --> COORD
EMIT --"wraps" --> TBS["Tabbar Selection"]
SRC --"updates all" --> VM
To understand an existing sRouting implementation, follow this research path:
@main and SRRootView — reveals the SRContext and root AppCoordinator.@sRouteCoordinator class — shows tabs, navigation stacks, and mapping.@sRoute or @sRoutePath enums — these are the source of truth for screens.SRRouter instances in Views and check @sRouteObserver for destination handling.For implementation details, see Sources/sRouting/Models/ (SRRouter, SRContext, SRNavigationPath) and Sources/sRoutingMacros/ (macro definitions).
| Macro | Purpose |
|---|---|
@sRoute |
Implements SRRoute protocol, generates Paths enum and path property |
@sRoutePath |
Like @sRoute but allows manual SRRoute conformance for custom logic |
@sSubRoute |
Marks a case as a nested route (e.g., case detail(DetailRoute)) |
@sRouteCoordinator |
Generates navigation path and tab selection properties, conforms to SRRouteCoordinatorType |
@sRouteObserver |
Generates a ViewModifier that handles navigation destinations |
import sRouting
import Observation
@sRouteCoordinator(tabs: ["home", "settings"], stacks: "home", "settings")
@Observable
final class AppCoordinator { }
@main
struct MyApp: App {
@State private var appCoordinator = AppCoordinator()
@State private var context = SRContext()
var body: some Scene {
WindowGroup {
SRRootView(context: context, coordinator: appCoordinator) {
SRSwitchView(startingWith: AppRoute.startScreen)
}
}
}
}
[!IMPORTANT]
SRRootViewmust be the root of your navigation hierarchy to enable deep linking and global navigation actions.
Checkpoint: Build and run — the app should launch without errors and display the initial screen from SRSwitchView.
@sRouteObserver(HomeRoute.self, SettingsRoute.self)
struct RouteObserver { }
Use coordinator.emitter.tabSelection for tab binding and apply .routeObserver to each NavigationStack.
struct MainTabbarView: View {
@Environment(AppCoordinator.self) var coordinator
var body: some View {
@Bindable var emitter = coordinator.emitter
TabView(selection: $emitter.tabSelection) {
NavigationStack(path: coordinator.homePath) {
HomeView()
.routeObserver(RouteObserver.self)
}
.tag(AppCoordinator.SRTabItem.homeItem)
.tabItem { Label("Home", systemImage: "house") }
NavigationStack(path: coordinator.settingsPath) {
SettingsView()
.routeObserver(RouteObserver.self)
}
.tag(AppCoordinator.SRTabItem.settingsItem)
.tabItem { Label("Settings", systemImage: "gear") }
}
}
}
Checkpoint: Build and run — tabs should appear and switching between them should work. Each tab's content view should render.
router.trigger(to: .detail, with: .push): Push a new screen.router.trigger(to: .profile, with: .sheet): Present a sheet.router.trigger(to: .login, with: .present): Present a full-screen cover.router.dismiss(): Dismiss the current modal or pop the top view.router.pop(): Pop one level in the current stack.router.popToRoot(): Pop all the way to the root of the current stack.router.show(alert: .error("Msg")): Show an alert (requires AlertRoute typealias in your Route enum).router.show(dialog: .confirm): Show a confirmation dialog.router.show(popover: .info): Show a popover.router.selectTabbar(at: .home): Programmatically switch tabs.router.switchTo(route: AppRoute.mainTabbar): Reset the root view (e.g., after login).router.dismissAll(): Dismiss all modals and reset path.Organize complex navigation by nesting routes.
@sRoute
enum HomeRoute {
case main
@sSubRoute case detail(DetailRoute)
var screen: some View {
switch self {
case .main: HomeView()
case .detail(let route): route.screen
}
}
}
Use SRContext to perform navigation from anywhere (e.g., in response to a push notification).
await context.routing(
.select(tabItem: .home),
.push(route: HomeRoute.detail(.info))
)
Present a new flow with its own coordinator (e.g., a multi-step onboarding).
// In your view
router.openCoordinator(route: OnboardingRoute.start, with: .present)
// In your root view
SRRootView(...) { ... }
.onRoutingCoordinator(OnboardingRoute.self, context: context)
| Symptom | Cause | Fix |
|---|---|---|
| Navigation push does nothing | Missing .routeObserver(RouteObserver.self) on the NavigationStack content |
Add .routeObserver(RouteObserver.self) to the root view inside each NavigationStack |
| Deep linking fails silently | SRRootView is not the root of the view hierarchy |
Ensure SRRootView wraps the entire app content in the @main struct |
| Tab selection binding has no effect | Using wrong tag type | Use AppCoordinator.SRTabItem enum values as tags, not raw strings |
| Sheet or alert does not appear | Route enum missing AlertRoute or DialogRoute typealias |
Add the required typealias to the route enum (e.g., typealias AlertRoute = MyAlertRoute) |