The navigation framework for SwiftUI.
sRouting provides a native navigation mechanism that simplifies handling navigation between screens.
- iOS 17 or above
- Xcode 16 or above
Explore DocC to find rich tutorials and get started with sRouting. See this WWDC presentation about more information.
From xCode select Product -> Build Doccumentation -> Explore.
Add sRouting
as a dependency to the project.
See this WWDC presentation for more information on how to adopt Swift packages in your app.
Specify https://github.com/ThangKM/sRouting.git
as the sRouting
package link.
Explore the example brach: Example
Set up the SRRootView
and interact with macros.
Create your root view using SRRootView
.
Declare your SRRoute
.
Learn about macros and ViewModifers.
To create a route, we must adhere to the SRRoute
Protocol.
@sRoute
enum HomeRoute {
typealias AlertRoute = YourAlertRoute // Optional declarations
typealias ConfirmationDialogRoute = YourConfirmationDialogRoute // Optional declarations
typealias PopoverRoute = YourPopoverRoute // Optional declarations
case pastry
case cake
@sSubRoute
case detail(DetailRoute)
@ViewBuilder @MainActor
var screen: some View {
switch self {
case .pastry: PastryScreen()
case .cake: CakeScreen()
case .detail(let route): route.screen
}
}
}
Start by configuring a coordinator and SRRootView for your application.
Declaring a Coordinator:
@sRouteCoordinator(tabs: ["home", "setting"], stacks: "home", "setting")
@Observable
final class AppCoordinator { }
Declaring the View for Navigation Destinations:
@sRouteObserver(HomeRoute.self, SettingRoute.self)
struct RouteObserver { }
Configuring Your App:
@sRoute
enum AppRoute {
case startScreen
case mainTabbar
@ViewBuilder @MainActor
var screen: some View {
switch self {
case .startScreen:
StartScreen()
.transition(.scale(scale: 0.1).combined(with: .opacity))
case .mainTabbar:
MainScreen()
.transition(.opacity)
}
}
}
struct MainScreen: View {
@Environment(AppCoordinator.self) var coordinator
var body: some View {
@Bindable var emitter = coordinator.emitter
TabView(selection: $emitter.tabSelection) {
NavigationStack(path: coordinator.homePath) {
HomeScreen()
.routeObserver(RouteObserver.self)
}
.tag(AppCoordinator.SRTabItem.homeItem.rawValue)
NavigationStack(path: coordinator.settingPath) {
SettingScreen()
.routeObserver(RouteObserver.self)
}
.tag(AppCoordinator.SRTabItem.settingItem.rawValue)
}
}
}
@main
struct BookieApp: 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)
}
.environment(appCoordinator)
}
}
}
Use the onRouting(of:)
view modifier to observe route transitions.
@sRoute
enum HomeRoute {
case detail
...
}
struct HomeScreen: View {
@State private var homeRouter = SRRouter(HomeRoute.self)
var body: some View {
VStack {
...
}
.onRouting(of: homeRouter)
}
DeepLink:
...
.onOpenURL { url in
Task {
...
await context.routing(.resetAll,
.select(tabItem: .home),
.push(route: HomeRoute.cake))
}
}
To observe and open a new coordinator from the router, use onRoutingCoordinator(_:context:)
.
Declaring Coordinator Routes:
@sRouteCoordinator(stacks: "newStack")
final class AnyCoordinator { }
struct AnyCoordinatorView<Content>: View where Content: View {
@Environment(SRContext.self) var context
@State private var coordinator: AnyCoordinator = .init()
let content: () -> Content
var body: some View {
SRRootView(context: context, coordinator: coordinator) {
NavigationStack(path: coordinator.newStackPath) {
content()
.routeObserver(YourRouteObserver.self)
}
}
}
}
@sRoute
enum CoordinatorsRoute {
case notifications
case settings
@MainActor @ViewBuilder
var screen: some View {
switch self {
case .notifications:
AnyCoordinatorView { NotificationsScreen() }
case .settings:
AnyCoordinatorView { SettingsScreen() }
}
}
}
Handling Coordinator Routing in the Root View:
Coordinators should be triggered from the root view using .onRoutingCoordinator.
@main
struct BookieApp: 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)
}
.environment(appCoordinator)
.onRoutingCoordinator(CoordinatorsRoute.self, context: context)
}
}
}
Present a new coordinator:
router.openCoordinator(route: CoordinatorsRoute.notifications, with: .present)
Change Root:
router.switchTo(route: AppRoute.mainTabbar)
Push:
router.trigger(to: .cake, with: .push)
NavigationLink:
NavigationLink(route: HomeRoute.pastry) {
...
}
Present full screen:
router.trigger(to: .cake, with: .present)
Sheet:
router.trigger(to: .cake, with: .sheet)
To show an alert we use the show(alert:)
function.
router.show(alert: YourAlertRoute.alert)
To dismiss a screen we use the dismiss()
function.
router.dismiss()
To dismiss to root view we use the dismissAll()
function.
Required the root view is a SRRootView
router.dismissAll()
To seclect the Tabbar item we use the selectTabbar(at:)
function.
router.selectTabbar(at: AppCoordinator.SRTabItem.home)
Pop Actions in NavigationStack
router.pop()
router.popToRoot()
router.pop(to: HomeRoute.Paths.cake)
sRouting
is released under an MIT license. See License.md for more information.