DEV Community

Cover image for Simplifying SwiftUI Navigation with SnazzyRouter: A Modern Approach to Routing
Dubon Ya'ar
Dubon Ya'ar

Posted on

Simplifying SwiftUI Navigation with SnazzyRouter: A Modern Approach to Routing

The Navigation Challenge in SwiftUI

If you've built anything beyond a simple SwiftUI app, you've likely encountered the navigation puzzle. NavigationStack, sheets, alerts, and popovers — each with their own APIs and state management quirks. What starts as a simple navigation flow quickly becomes a tangled web of @State variables and scattered navigation logic.

Let me show you a typical SwiftUI navigation scenario:

struct ContentView: View {
    @State private var showProfile = false
    @State private var showSettings = false
    @State private var selectedUserId: String?
    @State private var navigationPath = NavigationPath()
    @State private var showAlert = false
    @State private var alertMessage = ""

    var body: some View {
        NavigationStack(path: $navigationPath) {
            // Your content here
        }
        .sheet(isPresented: $showProfile) {
            if let userId = selectedUserId {
                ProfileView(userId: userId)
            }
        }
        .sheet(isPresented: $showSettings) {
            SettingsView()
        }
        .alert("Error", isPresented: $showAlert) {
            Button("OK") { }
        } message: {
            Text(alertMessage)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Sound familiar? This is just the beginning. Now imagine managing this across multiple views, handling deep links, and keeping track of complex navigation flows. It gets messy, fast.

Enter SnazzyRouter

SnazzyRouter is a modern, type-safe routing solution for SwiftUI that brings order to this chaos. It provides a clean, declarative API that consolidates all your navigation needs into a single, manageable system.

📦 Installation

First, add SnazzyRouter to your project via Swift Package Manager:

dependencies: [
    .package(url: "https://github.com/DubonYaar/SnazzyRouter.git", from: "1.0.0")
]
Enter fullscreen mode Exit fullscreen mode

🚀 Building Your First Router

Let's build a simple app with proper navigation. The first step is defining your routes:

import SnazzyRouter
import SwiftUI

enum AppRoute: Routable {
    case home
    case profile(userId: String)
    case settings
    case articleDetail(articleId: Int, title: String)

    @ViewBuilder
    var view: some View {
        switch self {
        case .home:
            HomeView()
        case .profile(let userId):
            ProfileView(userId: userId)
        case .settings:
            SettingsView()
        case .articleDetail(let articleId, let title):
            ArticleDetailView(articleId: articleId, title: title)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Notice how clean this is? All your routes in one place, with associated data passed type-safely. No more string-based navigation or lost context.

Setting Up the Router

Now, let's set up our app with the router:

import SwiftUI
import SnazzyRouter

@main
struct MyBlogApp: App {
    var body: some Scene {
        WindowGroup {
            Router(AppRoute.self) { router in
                HomeView()
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

That's it! Your app now has a fully functional routing system.

🎯 Navigation in Action

Let's see how navigation works in practice:

struct HomeView: View {
    @Environment(RouterState<AppRoute>.self) var router

    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                Text("Welcome to My Blog")
                    .font(.largeTitle)
                    .bold()

                ForEach(articles) { article in
                    ArticleCard(article: article)
                        .onTapGesture {
                            // Type-safe navigation with parameters
                            router.push(.articleDetail(
                                articleId: article.id,
                                title: article.title
                            ))
                        }
                }

                Button("View Profile") {
                    router.push(.profile(userId: currentUser.id))
                }
                .buttonStyle(.borderedProminent)
            }
            .padding()
        }
        .toolbar {
            ToolbarItem(placement: .topBarTrailing) {
                Button {
                    router.push(.settings)
                } label: {
                    Image(systemName: "gear")
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

📱 Modal Presentations Made Easy

One of SnazzyRouter's strengths is how it handles different presentation styles:

struct ArticleDetailView: View {
    let articleId: Int
    let title: String
    @Environment(RouterState<AppRoute>.self) var router

    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 16) {
                Text(title)
                    .font(.largeTitle)
                    .bold()

                // Article content...

                Button("Share") {
                    // Present as sheet
                    router.sheet = RouterModalItem(
                        destination: .share(articleId: articleId),
                        dismiss: {
                            print("Share sheet dismissed")
                        }
                    )
                }

                Button("View Author Profile") {
                    // Present as full screen cover
                    router.fullScreenCover = RouterModalItem(
                        destination: .profile(userId: article.authorId)
                    )
                }
            }
            .padding()
        }
        .toolbar {
            ToolbarItem(placement: .topBarTrailing) {
                Button("Report") {
                    // Show confirmation dialog
                    router.showConfirmationDialog(
                        title: "Report Article",
                        message: "Why are you reporting this article?",
                        actions: [
                            DialogAction(title: "Inappropriate Content") {
                                reportArticle(reason: .inappropriate)
                            },
                            DialogAction(title: "Spam") {
                                reportArticle(reason: .spam)
                            },
                            DialogAction(title: "Cancel", role: .cancel) { }
                        ]
                    )
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

🔥 Advanced Patterns

Deep Linking

SnazzyRouter makes deep linking straightforward:

class AppViewModel: ObservableObject {
    let router = RouterState<AppRoute>()

    func handleDeepLink(_ url: URL) {
        guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { return }

        switch components.path {
        case "/article":
            if let articleId = components.queryItems?.first(where: { $0.name == "id" })?.value,
               let id = Int(articleId) {
                router.popToRoot()
                router.push(.articleDetail(articleId: id, title: "Article"))
            }
        case "/profile":
            if let userId = components.queryItems?.first(where: { $0.name == "user" })?.value {
                router.popToRoot()
                router.push(.profile(userId: userId))
            }
        default:
            break
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Conditional Navigation

Sometimes you need to check conditions before navigating:

struct PremiumArticleCard: View {
    let article: Article
    @Environment(RouterState<AppRoute>.self) var router
    @EnvironmentObject var userSession: UserSession

    var body: some View {
        Button {
            if userSession.isPremium {
                router.push(.articleDetail(articleId: article.id, title: article.title))
            } else {
                router.showAlert(
                    Alert(
                        title: Text("Premium Content"),
                        message: Text("Upgrade to premium to read this article"),
                        primaryButton: .default(Text("Upgrade")) {
                            router.push(.subscription)
                        },
                        secondaryButton: .cancel()
                    )
                )
            }
        } label: {
            // Article card UI
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Navigation Stack Management

SnazzyRouter gives you full control over your navigation stack:

struct SettingsView: View {
    @Environment(RouterState<AppRoute>.self) var router

    var body: some View {
        List {
            Section("Account") {
                Button("Sign Out") {
                    signOut()
                    // Clear the entire navigation stack
                    router.popToRoot()
                }
            }

            Section("Debug") {
                Button("Current Stack Depth: \(router.path.count)") {
                    print("Current path: \(router.path)")
                }

                Button("Go Back 2 Screens") {
                    if router.path.count >= 2 {
                        router.path.removeLast(2)
                    }
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

💡 Why SnazzyRouter?

After using SnazzyRouter in production apps, here's what makes it stand out:

1. Type Safety

No more string-based navigation or runtime crashes. Your routes are checked at compile time.

2. Consolidated State

One source of truth for all navigation state — push navigation, modals, alerts, and dialogs.

3. Clean API

The declarative API feels natural in SwiftUI. No more juggling multiple @State variables.

4. Flexibility

Whether you need simple push navigation or complex modal flows, SnazzyRouter handles it elegantly.

5. Testability

With all navigation logic centralized, testing navigation flows becomes much easier.

🎬 Real-World Example: A Complete Flow

Let's look at a complete user flow to see how it all comes together:

struct CheckoutFlow: View {
    @Environment(RouterState<AppRoute>.self) var router
    @StateObject private var cart = ShoppingCart()

    var body: some View {
        VStack {
            // Cart items...

            Button("Proceed to Checkout") {
                if cart.items.isEmpty {
                    router.showAlert(
                        Alert(
                            title: Text("Empty Cart"),
                            message: Text("Add items to your cart first"),
                            dismissButton: .default(Text("OK"))
                        )
                    )
                } else if !userIsSignedIn {
                    // Show sign-in as modal
                    router.sheet = RouterModalItem(
                        destination: .signIn,
                        dismiss: {
                            // After sign in, continue to checkout
                            router.push(.checkout)
                        }
                    )
                } else {
                    router.push(.checkout)
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

🎯 Conclusion

SwiftUI's navigation APIs are powerful but can quickly become unwieldy in complex apps. SnazzyRouter brings structure and simplicity to navigation, letting you focus on building great user experiences rather than wrestling with navigation state.

The beauty of SnazzyRouter is that it doesn't try to reinvent the wheel — it builds on top of SwiftUI's native navigation while providing a cleaner, more maintainable API. Whether you're building a simple app or a complex navigation flow, SnazzyRouter scales with your needs.

Give it a try in your next SwiftUI project. Your future self (and your team) will thank you for the clean, maintainable navigation code.


Ready to get started? Check out SnazzyRouter on GitHub and make your SwiftUI navigation snazzy!


Have you tried SnazzyRouter in your projects? I'd love to hear about your experience! Drop a comment below or find me on Twitter/X.

GitHub logo DubonYaar / SnazzyRouter

A modern, type-safe router for SwiftUI with a clean declarative API

SnazzyRouter ✨

SnazzyRouter Banner

Navigation so smooth, it's got style
A modern, type-safe router for SwiftUI with a clean declarative API

📦 Installation

Swift Package Manager

Add SnazzyRouter to your project through Xcode:

  1. File → Add Package Dependencies...
  2. Enter the package URL: https://github.com/DubonYaar/SnazzyRouter.git
  3. Click Add Package

Or add it to your Package.swift:

dependencies: [
    .package(url: "https://github.com/DubonYaar/SnazzyRouter.git", from: "1.0.0")
]
Enter fullscreen mode Exit fullscreen mode

🚀 Quick Start

1. Define Your Routes

import SnazzyRouter
import SwiftUI
enum AppRoute: Routable {
    case home
    case profile(userId: String)
    case settings
    case detail(item: String)
  
    @ViewBuilder
    var view: some View {
        switch self {
        case .home:
            HomeView()
        case .profile(let userId):
            ProfileView(userId: userId)
        case .settings:
            SettingsView()
        case .detail(let item):
Enter fullscreen mode Exit fullscreen mode

Top comments (0)