/sRouting

The navigation lightweight framework for SwiftUI.

Primary LanguageSwiftMIT LicenseMIT

sRouting

Building Actions Status Platforms codecov.io Swift Package Manager compatible

The lightweight navigation framework for SwiftUI.

Overview

sRouting using the native navigation mechanism in SwiftUI. It's easy to handle navigation between screens by sRouting. The Router can trigger a transition from inside(view) or outside(view model) the view.

A sRouting banner.

Requirements

  • iOS 17 or above
  • Xcode 15 or above

📚 Documentation

Explore DocC to find the rich tutorials and getting started with sRouting. See this WWDC presentation about more information.

From xCode select Product -> Build Doccumentation -> Explore.

Or downloads the doccument archive from release

🛠 Installation

Add sRouting as a dependency to the project. See this WWDC presentation about more information how to adopt Swift packages in your app. Specify https://github.com/ThangKM/sRouting.git as the sRouting package link.

🏃‍♂️ Getting Started with sRouting

Set up SRRootView and working with sRouter(_:)

Overview

Create your root view with SRRootView. Declares your SRRoute. Working with sRContext(tabs:stacks:) macro, ScreenView and sRouter(_:) macro.

Create a Route

To create a route we have to conform to the SRRoute Protocol.

enum HomeRoute: SRRoute {
    case pastry
    case cake
    
    var path: String { 
        swich self {
            case .pastry: return "pastry"
            case .cake: return "cake"
        }
    }

    var screen: some View {
        switch self {
            case .pastry: PastryScreen()
            case .cake: CakeScreen()
        }
    }
}

Make your Root View

Setup a context and SRRootView for your app

Declaring Context:

@sRContext(tabs: ["home", "setting"], stacks: "home", "setting")
struct SRContext { }

Declaring View of navigation destination:

@sRouteObserve(HomeRoute.self, SettingRoute.self)
struct ObserveView<Content>: View where Content: View { }

Setup Your App:

@main
struct BookieApp: App { 
    let context = SRContext()
    ...
    var body: some Scene {

        WindowGroup {
            SRRootView(context: context) {
                SRTabbarView {
                    SRNavigationStack(path: context.homePath, observeView: ObserveView.self) {
                        AppRoute.home.screen
                    }.tabItem {
                        Label("Home", systemImage: "house")
                    }.tag(SRTabItem.home.rawValue)
                    
                    SRNavigationStack(path: context.settingPath, observeView: ObserveView.self) {
                        AppRoute.setting.screen
                    }.tabItem {
                        Label("Setting", systemImage: "gear")
                    }.tag(SRTabItem.setting.rawValue)
                }
                .onDoubleTapTabItem { ... }
                .onTabSelectionChange { ... }
            }
            .onOpenURL { url in
                Task {
                    ...
                    await context.routing(.resetAll,.select(tabItem: .home),
                                          .push(route: HomeRoute.cake, into: .home))
                }
            }
        }
    }
}

Make a Screen and working with Router

Build a screen with ScreenView, ScreenView will create a hidden NavigatorView at below content view in a ZStack. The NavigatorView will handle transactions that are emited by Router

enum HomeRoute: SRRoute {
    case detail
    ...
}

@sRouter(HomeRoute.self)
class HomeViewModel { ... }

struct HomeScreen: View {

    @Environment(\.dismiss)
    private var dismissAction

    @State let viewModel = HomeViewModel()

    var body: some View {
        ScreenView(router: viewModel, dismissAction: dismissAction) {
        ...
        }
    }

To navigate to a screen that must be in HomeRoute we use the trigger(to:with:) function in the Router

DeepLink:

...
.onOpenURL { url in
    Task {
        ...
        await context.routing(.resetAll,.select(tabItem: .home),
                              .push(route: HomeRoute.cake, into: .home))
    }
}

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:  Alert.init(title: Text("Alert"),
                                message: Text("Message"),
                                dismissButton: .cancel(Text("OK")))

To show an error message we use the show(error:and:) function.

router.show(error:NetworkingError.lossConnection)

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:0)

sRouting also supported pop, pop to root and pop to a target function for the NavigationView

router.pop()

router.popToRoot()

router.pop(to: HomeRoute.cake)

Conclusion

sRouting is a lightweight framework and flexiable.

📃 License

sRouting is released under an MIT license. See License.md for more information.