Back to OSS
Swift Package UI / SwiftUI
swift-ui-routing
SwiftUI 向けの型安全で宣言的なルーティングライブラリ
Swift
swiftuiroutingnavigation
English | 日本語
UIRouting
Type-safe routing library for SwiftUI.
Features
// Push navigation
router.navigate(to: .detail(id: "123"))
// Sheet presentation
sheetPresenter.present(.settings)
// Alert presentation
alertPresenter.present(.deleteConfirmation { /* ... */ })
- Type-safe — all transitions verified at compile time
- Concise — instant access via
@Environment - Full coverage — Navigation, Sheet, FullScreenCover, CustomHeightSheet, Alert, Tab, SplitView
Installation
// Package.swift
dependencies: [
.package(url: "https://github.com/no-problem-dev/swift-ui-routing.git", from: "2.1.0")
]
Or via Xcode: File > Add Package Dependencies > enter URL.
Basic Usage
1. Define routes
enum AppRoute: Routable {
case detail(id: String)
var id: String { "detail_\(id)" }
var body: some View { DetailView(id: id) }
}
enum AppSheet: Sheetable {
case settings
var id: String { "settings" }
var body: some View { SettingsView() }
}
enum AppAlert: Alertable {
case delete(onConfirm: () -> Void)
var title: String { "Delete?" }
var message: String? { nil }
var actions: [AlertAction] {
switch self {
case .delete(let onConfirm):
return [
AlertAction(title: "Cancel", role: .cancel) {},
AlertAction(title: "Delete", role: .destructive, action: onConfirm)
]
}
}
}
2. Setup
// App: create Router and Presenters, inject into environment
@main
struct MyApp: App {
@State private var router = Router<AppRoute>()
@State private var sheetPresenter = SheetPresenter<AppSheet>()
@State private var alertPresenter = AlertPresenter<AppAlert>()
var body: some Scene {
WindowGroup {
ContentView()
.routing(
router: router,
sheetPresenter: sheetPresenter,
alertPresenterOnNavigation: alertPresenter,
alertPresenterOnSheet: AlertPresenter<AppAlert>()
)
}
}
}
// ContentView: set up the NavigationStack root
struct ContentView: View {
var body: some View {
HomeView()
.routingScope(for: AppRoute.self, alert: AppAlert.self)
}
}
3. Use in views
struct HomeView: View {
@Environment(.router(AppRoute.self)) private var router
@Environment(.sheet(AppSheet.self)) private var sheetPresenter
@Environment(.alert(AppAlert.self, context: .navigation)) private var alertPresenter
var body: some View {
Button("Detail") { router.navigate(to: .detail(id: "123")) }
Button("Settings") { sheetPresenter.present(.settings) }
Button("Delete") { alertPresenter.present(.delete { print("deleted") }) }
}
}
TabView
enum AppTab: Tabbable {
case home, settings
typealias Route = AppRoute
typealias Sheet = AppSheet
var contentView: some View {
switch self {
case .home: HomeView()
case .settings: SettingsView()
}
}
var tabLabel: some View {
switch self {
case .home: Label("Home", systemImage: "house")
case .settings: Label("Settings", systemImage: "gearshape")
}
}
}
// Setup
@State private var tabPresenter = TabPresenter(initialTab: AppTab.home)
TabRouting(tabPresenter: tabPresenter, tabs: [.home, .settings])
Cross-tab navigation (switch tab then push):
@Environment(.tab(AppTab.self)) private var tabPresenter
// Switch tab
tabPresenter.select(.home)
// Switch tab and navigate
tabPresenter.select(.home) { context in
context.router.navigate(to: .detail(id: "123"))
}
Modal Presentations
FullScreenCover
enum AppFullScreenCover: FullScreenCoverable {
case camera
case editor(id: String)
var id: String {
switch self {
case .camera: return "camera"
case .editor(let id): return "editor_\(id)"
}
}
var body: some View {
switch self {
case .camera: CameraView()
case .editor(let id): EditorView(id: id)
}
}
}
// Setup: inject into environment via .routing()
@State private var fullScreenCoverPresenter = FullScreenCoverPresenter<AppFullScreenCover>()
ContentView()
.routing(
router: Router<AppRoute>(),
sheetPresenter: SheetPresenter<AppSheet>(),
customHeightSheetPresenter: CustomHeightSheetPresenter<Never>(),
fullScreenCoverPresenter: fullScreenCoverPresenter,
alertPresenterOnNavigation: AlertPresenter<AppAlert>(),
alertPresenterOnSheet: AlertPresenter<AppAlert>(),
splitViewPresenter: SplitViewPresenter<Never>()
)
// Use
@Environment(.fullScreenCover(AppFullScreenCover.self)) private var presenter
presenter.present(.camera)
CustomHeightSheet
enum AppCustomSheet: CustomHeightSheetable {
case picker
case quickAdd
var id: String {
switch self {
case .picker: return "picker"
case .quickAdd: return "quickAdd"
}
}
var body: some View {
switch self {
case .picker: PickerView()
case .quickAdd: QuickAddView()
}
}
var detents: Set<PresentationDetent> {
switch self {
case .picker: return [.medium, .large]
case .quickAdd: return [.height(200)]
}
}
}
// Setup
@State private var presenter = CustomHeightSheetPresenter<AppCustomSheet>()
ContentView()
.customHeightSheet(presenter: presenter)
// Use
@Environment(.customHeightSheet(AppCustomSheet.self)) private var presenter
presenter.present(.picker)
NavigationSplitView
2-column (sidebar + detail)
enum Sidebar: SidebarItem {
case inbox, sent
typealias DetailRoute = MailRoute
var label: some View { Label("Inbox", systemImage: "tray") }
var detail: some View { InboxView() }
}
@State private var presenter = SplitViewPresenter<Sidebar>(initialSelection: .inbox)
SplitViewRouting(splitViewPresenter: presenter, items: [.inbox, .sent])
3-column (sidebar + list + detail)
enum Sidebar: SidebarItem {
case inbox
typealias ContentItem = Email // selectable item in center column
typealias ContentRoute = FilterRoute // navigation within center column
typealias DetailRoute = MailRoute // navigation within detail column
var label: some View { Label("Inbox", systemImage: "tray") }
var contentView: some View { MailListView() } // center column
var detail: some View { MailDetailView() } // detail column
}
@State private var presenter = SplitViewPresenter<Sidebar>(initialSelection: .inbox)
ThreeColumnSplitViewRouting(splitViewPresenter: presenter, items: [.inbox])
Read selected item in the center column:
@Environment(.selectedContentBinding(Email.self)) private var selectedContentBinding
List(selection: selectedContentBinding) {
ForEach(emails) { email in
NavigationLink(value: email) { email.label }
}
}
API Reference
Router
router.navigate(to: .detail) // push
router.back() // pop
router.popToRoot() // pop to root
router.replace(with: .profile) // replace stack
Presenter
sheetPresenter.present(.settings) // sheet
fullScreenCoverPresenter.present(.editor) // full-screen cover
customHeightSheetPresenter.present(.picker) // custom height sheet
alertPresenter.present(.error("Error")) // alert
tabPresenter.select(.search) // tab switch
splitViewPresenter.select(.inbox) // sidebar selection
Examples
See complete examples:
- TodoExample — Navigation, Sheet, Alert, TabView, FullScreenCover, CustomHeightSheet
- MailExample — 3-column NavigationSplitView
Requirements
- iOS 17.0+ / macOS 14.0+
- Swift 6.0+
License
MIT License — see LICENSE for details.
Support
Report issues and feature requests on GitHub Issues.
同じカテゴリの OSS — UI / SwiftUI
swift-design-system
Swift PackageSwiftUI 向けの型安全で拡張可能なデザインシステム
Swift
· UI / SwiftUIswiftuidesign-systemios
swift-statable
Swift PackageAsyncValue パターンで Observable な状態管理を実現する Swift マクロ
Swift
· UI / SwiftUIswift-macrostate-managementasync
swift-markdown-view
Swift PackageDesignSystem 統合とシンタックスハイライトを備えた SwiftUI ネイティブな Markdown レンダリング
Swift
· UI / SwiftUIswiftuimarkdownsyntax-highlighting
swift-latex-view
Swift PackageSwiftUI ネイティブな LaTeX 数式レンダリング。LLM 出力にも堅牢
Swift
· UI / SwiftUIswiftuilatexmath
swift-cached-remote-image
Swift Packageメモリ & ディスクの二層キャッシュで高速表示する SwiftUI リモート画像
Swift
· UI / SwiftUIswiftuiimage-cacheasync
swift-google-slides-view
Swift PackageGoogle Slides API の JSON を SwiftUI で描画。A2A アーティファクトのストリーミングに対応
Swift
· UI / SwiftUIswiftuigoogle-slidesa2a
swift-document-scanner
Swift Package矩形検出・OCR・カメラ撮影を備えた iOS 向けドキュメントスキャン基盤
Swift
· UI / SwiftUIiosscannerocr
swift-voice-input
Swift Packageストリーミング認識とフローティングプレビュー UI を備えたプロトコル指向の音声入力
Swift
· UI / SwiftUIvoicespeechswiftui