← Back to list

I Built a SwiftUI Navigation Library Because NavigationLink Doesn’t Scale

SwiftUI is powerful, but if you’ve built anything beyond a demo app, you’ve probably struggled with navigation.

Mayank Kankrecha · 2026-03-03 18:57 · 2 claps · 2.3 min read
#swiftui #ios-development #mvvm-c #ios #swift
Open on Medium ↗
Wiki topics: 📱 · Mobile Development 📚 · Books & Reading

I Built a SwiftUI Navigation Library Because NavigationLink Doesn’t Scale

SwiftUI is powerful, but if you’ve built anything beyond a demo app, you’ve probably struggled with navigation.

After dealing with real-world SwiftUI projects, I decided to build a small open-source library called MKNavigatation — a ViewModel-driven, SwiftUI-native navigation solution inspired by the Coordinator pattern.

This article explains:

  • Why SwiftUI navigation becomes painful
  • What problem MKNavigatation solves
  • The architecture behind it
  • How you can use it in your own apps

The Problem with SwiftUI Navigation

SwiftUI gives us tools like:

  • NavigationLink
  • NavigationStack
  • NavigationPath

They work fine… until your app grows.

Common issues I faced:

  • Navigation logic leaking into Views
  • Conditional NavigationLinks everywhere
  • Hard-to-test navigation flows
  • Difficult “pop to root” or “reset stack” logic
  • Poor separation of concerns in MVVM

In short:

Navigation becomes tightly coupled with UI.

This goes against clean architecture and makes scaling painful.

Why Not Use the Classic Coordinator Pattern?

The Coordinator pattern is great in UIKit, but in SwiftUI:

  • It often relies on UIKit bridges
  • It fights SwiftUI’s state-driven nature
  • It adds unnecessary complexity

I wanted:

  • A SwiftUI-native solution
  • No UIKit
  • No NavigationLink in Views
  • Navigation controlled from ViewModels

That’s how MKNavigatation was born.

Core Idea Behind MKNavigatation

The idea is simple:

***- Views should never decide navigation.

  • ViewModels should express intent.
  • A Router should own navigation state.***

So the flow becomes:

User Action
   ↓
View
   ↓
ViewModel
   ↓
NavigationRouter
   ↓
NavigationStack

This keeps responsibilities clean and predictable.

Architecture Overview

MKNavigatation is built around three core concepts:

1. Route

A route is an enum that represents screens.

enum AppRoute: NavigationRoute {
    case home
    case profile(userId: String)
    case settings
}

Routes are:

  • Type-safe
  • Easy to reason about
  • Centralized

2. NavigationRouter (Coordinator-like)

The router:

  • Owns NavigationPath
  • Exposes simple navigation APIs
  • Is observable by SwiftUI
final class NavigationRouter<Route: NavigationRoute>: ObservableObject {
    @Published var path = NavigationPath()

    func push(_ route: Route) {
        path.append(route)
    }

    func pop() {
        path.removeLast()
    }

    func popToRoot() {
        path.removeLast(path.count)
    }
}

This is the heart of navigation.

3. NavigationContainer

A simple wrapper around NavigationStack:

NavigationStack(path: $router.path) {
    content
}

This connects SwiftUI to the router without polluting views.

ViewModel-Driven Navigation (The Key Benefit)

Navigation is triggered only from ViewModels:

final class HomeViewModel {
    private let router: NavigationRouter<AppRoute>

    init(router: NavigationRouter<AppRoute>) {
        self.router = router
    }

    func openProfile() {
        router.push(.profile(userId: "101"))
    }
}

And the View stays clean:

struct HomeView: View {
    let viewModel: HomeViewModel

    var body: some View {
        Button("Go to Profile") {
            viewModel.openProfile()
        }
    }
}

No NavigationLink. No routing logic in the UI. Just intent.

Why This Scales Well

This approach works especially well for:

  • Medium to large SwiftUI apps
  • Modular architectures (SPM + feature modules)
  • MVVM or Clean Architecture
  • Teams that care about testability
  • Interview-ready system design

It also makes:

  • Deep linking
  • Stack replacement
  • Analytics tracking much easier to add later.

When You Should (and Shouldn’t) Use This

✅ Use MKNavigatation if:

  • Your app has multiple flows
  • You want clean MVVM separation
  • Navigation logic is growing
  • You care about testability

❌ Don’t use it if:

  • Your app has one or two screens
  • You’re building a throwaway demo

Open Source & Future Plans

MKNavigatation is open-source and intentionally minimal.

Planned improvements:

  • Modal & sheet navigation
  • Deep linking support
  • Multi-router per feature
  • Navigation analytics hooks

You can check out the repository here: 👉 **https://github.com/MayannkKankrecha/MKNavigatation**

Final Thoughts

SwiftUI navigation doesn’t need to be messy.

By:

  • Moving navigation out of Views
  • Letting ViewModels express intent
  • Centralizing state in a Router

…you get a system that’s clean, scalable, and easy to reason about.

If you’ve struggled with SwiftUI navigation, I hope **MKNavigatation** helps you the same way it helped me.

Thanks for reading 👋 Happy coding 🚀


메타데이터
post_id
ffc28dc76e38
slug
i-built-a-swiftui-navigation-library-because-navigationlink-doesnt-scale-ffc28dc76e38
url
https://medium.com/@kankrechamayank/i-built-a-swiftui-navigation-library-because-navigationlink-doesnt-scale-ffc28dc76e38
canonical_url
https://medium.com/@kankrechamayank/i-built-a-swiftui-navigation-library-because-navigationlink-doesnt-scale-ffc28dc76e38
author_url
https://medium.com/@kankrechamayank
status
ok
fetched_at
2026-06-27 23:56:40