Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

A type-safe, MVVM-C navigation library for SwiftUI with @observable coordinators, stack and nested modal routing, state restoration, and deep-link support.

Notifications You must be signed in to change notification settings

Erikote04/SwiftNavigation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

31 Commits

Repository files navigation

SwiftNavigation

Type-safe, coordinator-driven navigation for SwiftUI apps using only native APIs (NavigationStack, .sheet, .fullScreenCover).

Swift Platform DocC Deploy Documentation

Table of Contents

Overview

SwiftNavigation provides a modular MVVM-C navigation layer with:

  • Pure SwiftUI navigation and presentation APIs.
  • @Observable + @MainActor coordinator state.
  • Type-safe stack operations over plain route arrays.
  • Nested modal flows with independent internal stacks.
  • Codable snapshots for restoration and deep-link reconstruction.

Key Features

  • Type-safe root stack ([Route]) with push, pop, popToRoot, and popToRoute.
  • Recursive modal navigation (sheet + fullScreenCover) with internal stack state per modal.
  • Automatic state sync when users dismiss modals via native gestures.
  • Scoped coordinators (application, feature(name:), tab(name:)) and child lifecycle cleanup.
  • Protocol-based routing contracts to decouple ViewModels from SwiftUI.
  • Swift Testing-friendly architecture for deterministic navigation tests.

Requirements

  • iOS 17+
  • Swift 6.1+
  • Xcode 16+

Installation

Xcode

In Xcode:

  1. File -> Add Packages...
  2. Enter: https://github.com/Erikote04/SwiftNavigation
  3. Select Up to Next Major Version and set it to 1.0.0
  4. Add the SwiftNavigation product to your target

Package.swift

dependencies: [
 .package(url: "https://github.com/Erikote04/SwiftNavigation", from: "1.0.0")
]

Quick Start

1. Define Routes

import SwiftNavigation
enum AppRoute: String, NavigationRoute {
 case home
 case detail
 case settings
}
enum AppModalRoute: String, NavigationRoute {
 case login
 case terms
}

2. Create a Coordinator

import SwiftNavigation
@MainActor
let coordinator = NavigationCoordinator<AppRoute, AppModalRoute>(scope: .application)

3. Build the Routing Container

import SwiftUI
import SwiftNavigation
@MainActor
struct AppRootView: View {
 @State private var coordinator = NavigationCoordinator<AppRoute, AppModalRoute>(scope: .application)
 var body: some View {
 RoutingView(
 coordinator: coordinator,
 root: {
 HomeView(
 onOpenDetail: { coordinator.push(.detail) },
 onOpenLogin: { coordinator.present(.login, style: .sheet) }
 )
 },
 stackDestination: { route in
 switch route {
 case .home:
 HomeView(onOpenDetail: {}, onOpenLogin: {})
 case .detail:
 DetailView()
 case .settings:
 SettingsView()
 }
 },
 modalDestination: { route in
 switch route {
 case .login:
 LoginView()
 case .terms:
 TermsView()
 }
 }
 )
 .navigationCoordinator(coordinator)
 }
}

MVVM-C Decoupling

Create a feature-focused routing protocol and inject it into your ViewModel.

import SwiftNavigation
@MainActor
protocol AuthRouting: AnyObject {
 func showTerms()
 func finishLogin()
}
@MainActor
final class AuthRouter: AuthRouting {
 private let router: NavigationRouterProxy<AppRoute, AppModalRoute>
 init(coordinator: NavigationCoordinator<AppRoute, AppModalRoute>) {
 self.router = NavigationRouterProxy(coordinator: coordinator)
 }
 func showTerms() {
 router.present(.terms, style: .fullScreen)
 }
 func finishLogin() {
 router.dismissTopModal()
 router.push(.home)
 }
}

State Restoration

let snapshot = coordinator.exportState()
let data = try JSONEncoder().encode(snapshot)
let restored = try JSONDecoder().decode(
 NavigationState<AppRoute, AppModalRoute>.self,
 from: data
)
coordinator.restore(from: restored)

Deep Linking

import Foundation
import SwiftNavigation
struct URLResolver: URLDeepLinkResolving {
 func navigationState(for url: URL) throws -> NavigationState<AppRoute, AppModalRoute> {
 if url.host == "settings" {
 return NavigationState(stack: [.home, .settings])
 }
 return NavigationState(stack: [.home])
 }
}
try coordinator.applyURLDeepLink(URL(string: "myapp://settings")!, resolver: URLResolver())

Testing

The coordinator is designed for direct state assertions with Swift Testing.

import Testing
@testable import SwiftNavigation
@Test @MainActor
func pushAndPop() {
 let coordinator = NavigationCoordinator<AppRoute, AppModalRoute>(scope: .feature(name: "test"))
 coordinator.push(.home)
 coordinator.push(.detail)
 #expect(coordinator.stack == [.home, .detail])
 _ = coordinator.pop()
 #expect(coordinator.stack == [.home])
}

Documentation

About

A type-safe, MVVM-C navigation library for SwiftUI with @observable coordinators, stack and nested modal routing, state restoration, and deep-link support.

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

AltStyle によって変換されたページ (->オリジナル) /