NavigationKit
A SwiftUI navigation package for iOS 16+
Install / Use
/learn @ahmedelmoughazy/NavigationKitREADME
NavigationKit
A lightweight navigation system for SwiftUI applications that provides programmatic navigation with hierarchical state management.
Demo
Features
- Type-Safe Navigation: Strongly-typed routing with compile-time safety
- Layered Navigation: Present views on top of each other, modals within modals, all managed automatically
- Modal Presentations: Built-in support for sheets and full-screen covers
- Programmatic Control: Push, pop, present, and dismiss from anywhere
- Debug Support: Comprehensive hierarchy logging for development
- Reactive Updates: Combine publishers for navigation state changes
- Animation Control: Optional animation for all navigation actions
Requirements
- iOS 16.0+
- Swift 5.9+
- Xcode 15.0+
Installation
Swift Package Manager
Add NavigationKit to your project using Swift Package Manager:
dependencies: [
.package(url: "https://github.com/ahmedelmoughazy/NavigationKit.git", from: "0.2.0")
]
Then add it to your target dependencies:
.target(
name: "YourTarget",
dependencies: ["NavigationKit"]
)
Quick Start
1. Create Your Views
Mark your views with the @Routable macro:
import SwiftUI
import NavigationKit
@Routable
struct ProfileView: View {
let userId: String
var body: some View {
Text("Profile for user: \(userId)")
}
}
@Routable
struct SettingsView: View {
var body: some View {
Text("Settings")
}
}
2. Set Up Navigation
Create a router and wrap your root view with BaseNavigation:
import SwiftUI
import NavigationKit
@main
struct MyApp: App {
private let router = Router()
var body: some Scene {
WindowGroup {
BaseNavigation(router: router) {
RootView()
}
}
}
}
3. Navigate
Access the router from any view using @EnvironmentObject and navigate programmatically:
struct RootView: View {
@EnvironmentObject var router: Router
var body: some View {
VStack {
Button("Go to Profile") {
router.push(destination: ProfileView(userId: "123"))
}
Button("Open Settings as Sheet") {
router.present(destination: SettingsView(), as: .sheet)
}
}
}
}
Usage Guide
Router API
Stack Navigation
// Push a new destination
router.push(destination: ProfileView(userId: "123"))
// Pop the current view
router.pop()
// Pop to a specific destination
router.pop(to: HomeView())
// Pop to presentation (clear current stack)
router.popToPresentation()
// Pop all and dismiss all modals
router.popAll()
Modal Presentation
// Present as sheet
router.present(destination: SettingsView(), as: .sheet)
// Present as full-screen cover
router.present(destination: DetailView(), as: .fullScreenCover)
// Dismiss current modal
router.dismiss()
Advanced Navigation
// Insert destination at specific index
router.insert(destination: HomeView(), at: 0)
// Remove specific destinations
router.remove(destinations: ProfileView(userId: "123"))
// Replace entire navigation path
router.applyPath([HomeView(), ProfileView(userId: "456")])
Animation Control
All navigation methods support optional animation control:
router.push(destination: .profileView(userId: "123"), animated: false)
router.pop(animated: false)
router.present(destination: .settingsView, as: .sheet, animated: false)
Route Tracking
Monitor navigation state changes reactively:
// Get current route
let currentRoute: [String] = router.currentRoute
// Subscribe to route changes
router.currentRoutePublisher
.sink { route in
print("Navigation changed: \(route)")
}
.store(in: &cancellables)
Alert System
NavigationKit includes a built-in alert system that works seamlessly with modal presentations.
Why a Custom Alert System?
When using standard SwiftUI alert modifiers alongside sheet or full-screen cover presentations, showing an alert can cause the modal presentation to be dismissed unexpectedly. This is a known SwiftUI behavior where alert presentation can interfere with modal lifecycle management.
NavigationKit's alert system solves this problem by:
- Modal-Safe Presentation: Alerts won't dismiss sheets or full-screen covers
- State Management: Properly integrated with the navigation hierarchy
- Alert Replacement: Seamlessly replace one alert with another
- Automatic Cleanup: Properly synchronizes state with the router
Usage
Present alerts through the router's alertItem property:
struct MyView: View {
@EnvironmentObject var router: Router<Route>
var body: some View {
Button("Show Alert") {
router.presentAlert(alertItem: AlertItem(
title: "Confirmation",
message: "Are you sure?",
actionButtons: [
AlertActionButton(title: "Confirm", style: .primary) {
// Handle confirmation
},
AlertActionButton(title: "Cancel", style: .cancel)
]
)
)
}
}
}
Alert Button Styles
NavigationKit provides four button styles to match your alert's intent:
.primary: The main action (emphasized with keyboard shortcut).secondary: Alternative actions (default style).destructive: Dangerous actions like delete.cancel: Dismisses without action
Dismissing Alerts
Alerts are automatically dismissed when:
- The user taps any action button.
- You call
router.dismissAlert().
Advanced Features
Debug Logging
NavigationKit provides powerful logging capabilities to help debug navigation flows.
Configuring Logging
Set the logging style when creating the router:
// Disable logging (default)
let router = Router(loggingStyle: .disabled)
// Enable hierarchical logging (tree view)
let router = Router(loggingStyle: .hierarchical)
// Enable flat logging (array view)
let router = Router(loggingStyle: .flat)
When logging is enabled, the navigation hierarchy is automatically printed whenever navigation state changes.
Logging Styles
Disabled (default) - No logging output
Hierarchical - Tree view with indentation:
🎯 Router#a1b2c
📱 Path: [home, profile]
📄 Sheet: settings
└── 🎯 Router#d3e4f
📱 Path: [details]
Flat - Array view with sequential listing:
Routers: [
🎯 Router#a1b2c | 📱 Path: [home, profile] | 📄 Sheet: settings
🎯 Router#d3e4f | 📱 Path: [details]
]
Dynamic Configuration
You can change the logging style at any time:
// Switch to hierarchical logging
router.loggingStyle = .hierarchical
// Disable logging
router.loggingStyle = .disabled
Manual Logging
You can also manually trigger logging at any time:
router.debugPrintCompleteHierarchy()
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
NavigationKit is available under the MIT license. See the LICENSE file for more info.
Author
Ahmed Elmoughazy - @ahmedelmoughazy
Acknowledgments
Built with Swift Macros and SwiftSyntax for powerful compile-time code generation.
Related Skills
node-connect
344.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
99.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
344.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
344.4kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
