Back to OSS
Swift Package UI / SwiftUI

swift-ui-routing

SwiftUI 向けの型安全で宣言的なルーティングライブラリ

Swift
swiftuiroutingnavigation

English | 日本語

UIRouting

Type-safe routing library for SwiftUI.

Swift Platforms License

📚 Full Documentation

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 Package

SwiftUI 向けの型安全で拡張可能なデザインシステム

Swift
· UI / SwiftUI
swiftuidesign-systemios

swift-statable

Swift Package

AsyncValue パターンで Observable な状態管理を実現する Swift マクロ

Swift
· UI / SwiftUI
swift-macrostate-managementasync

swift-markdown-view

Swift Package

DesignSystem 統合とシンタックスハイライトを備えた SwiftUI ネイティブな Markdown レンダリング

Swift
· UI / SwiftUI
swiftuimarkdownsyntax-highlighting

swift-latex-view

Swift Package

SwiftUI ネイティブな LaTeX 数式レンダリング。LLM 出力にも堅牢

Swift
· UI / SwiftUI
swiftuilatexmath

メモリ & ディスクの二層キャッシュで高速表示する SwiftUI リモート画像

Swift
· UI / SwiftUI
swiftuiimage-cacheasync

Google Slides API の JSON を SwiftUI で描画。A2A アーティファクトのストリーミングに対応

Swift
· UI / SwiftUI
swiftuigoogle-slidesa2a

swift-document-scanner

Swift Package

矩形検出・OCR・カメラ撮影を備えた iOS 向けドキュメントスキャン基盤

Swift
· UI / SwiftUI
iosscannerocr

swift-voice-input

Swift Package

ストリーミング認識とフローティングプレビュー UI を備えたプロトコル指向の音声入力

Swift
· UI / SwiftUI
voicespeechswiftui

© 2026 Kyoichi Taniguchi. All rights reserved.