Back to OSS

swift-ui-routing

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

Swift
swiftuiroutingnavigation

UIRouting

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

Swift Platforms License

📚 完全なドキュメント

特徴

// 画面遷移
router.navigate(to: .detail(id: "123"))

// シート表示
sheetPresenter.present(.settings)

// アラート表示
alertPresenter.present(.deleteConfirmation { /* ... */ })
  • 型安全 - 全ての遷移をコンパイル時に検証
  • 簡潔 - @Environmentで即座にアクセス
  • 完全対応 - Navigation, Sheet, FullScreenCover, CustomHeightSheet, Alert, Tab, SplitView

インストール

// Package.swift
dependencies: [
    .package(url: "https://github.com/no-problem-dev/swift-ui-routing.git", from: "1.0.0")
]

または Xcode: File > Add Package Dependencies > URL入力

基本的な使い方

1. ルート定義

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 { "削除しますか?" }
    var actions: [AlertAction] {
        [.cancel, .destructive("削除", action: onConfirm)]
    }
}

2. セットアップ

@main
struct MyApp: App {
    @State private var router = Router<AppRoute>()
    @State private var sheetPresenter = SheetPresenter<AppSheet>()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .routing(router: router, sheetPresenter: sheetPresenter)
                .routingScope(for: AppRoute.self)
                .sheet(item: $sheetPresenter.presentedSheet) { $0.body }
        }
    }
}

3. ビューで使用

struct ContentView: 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("詳細へ") { router.navigate(to: .detail(id: "123")) }
        Button("設定") { sheetPresenter.present(.settings) }
        Button("削除") { alertPresenter.present(.delete { print("削除") }) }
    }
}

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("ホーム", systemImage: "house")
        case .settings: Label("設定", systemImage: "gearshape")
        }
    }
}

// アプリでセットアップ
@State private var tabPresenter = TabPresenter(initialTab: AppTab.home)

TabRouting(tabPresenter: tabPresenter, tabs: [.home, .settings])

クロスタブナビゲーション(別タブに切り替えて画面遷移):

@Environment(.tab(AppTab.self)) private var tabPresenter

// タブ切り替え
tabPresenter.select(.home)

// タブ切り替え + 画面遷移
tabPresenter.select(.home) { context in
    context.router.navigate(to: .detail(id: "123"))
}

モーダル表示

FullScreenCover(フルスクリーン)

enum AppFullScreenCover: Identifiable, Hashable {
    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)
        }
    }
}

// セットアップ
@State private var presenter = FullScreenCoverPresenter<AppFullScreenCover>()

ContentView()
    .fullScreenCover(item: $presenter.presentedCover) { $0.body }

// ビューで使用
@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)]
        }
    }
}

// セットアップ
@State private var presenter = CustomHeightSheetPresenter<AppCustomSheet>()

ContentView()
    .customHeightSheet(presenter: presenter)

// ビューで使用
@Environment(.customHeightSheet(AppCustomSheet.self)) private var presenter
presenter.present(.picker)

NavigationSplitView対応

2カラム(サイドバー + 詳細)

enum Sidebar: SidebarItem {
    case inbox, sent

    typealias DetailRoute = MailRoute

    var label: some View { Label("受信箱", systemImage: "tray") }
    var detail: some View { InboxView() }
}

@State private var presenter = SplitViewPresenter<Sidebar>(initialSelection: .inbox)

SplitViewRouting(splitViewPresenter: presenter, items: [.inbox, .sent])

3カラム(サイドバー + リスト + 詳細)

enum Sidebar: SidebarItem {
    case inbox

    typealias ContentItem = Email         // 中央カラムの選択可能アイテム
    typealias ContentRoute = FilterRoute  // 中央カラム内のナビゲーション
    typealias DetailRoute = MailRoute     // 詳細内のナビゲーション

    var label: some View { Label("受信箱", systemImage: "tray") }
    var contentView: some View { MailListView() }  // 中央カラム
    var detail: some View { MailDetailView() }     // 詳細カラム
}

@State private var presenter = SplitViewPresenter<Sidebar>(initialSelection: .inbox)

ThreeColumnSplitViewRouting(splitViewPresenter: presenter, items: [.inbox])

中央カラムで選択されたアイテムを取得:

// 中央カラムのビュー
@Environment(.selectedContentBinding(Email.self)) private var selectedContentBinding

List(selection: selectedContentBinding) {
    ForEach(emails) { email in
        NavigationLink(value: email) { email.label }
    }
}

API一覧

Router

router.navigate(to: .detail)    // 画面遷移
router.back()                   // 戻る
router.popToRoot()              // ルートへ
router.replace(with: .profile)  // 置き換え

Presenter

sheetPresenter.present(.settings)          // シート
fullScreenCoverPresenter.present(.editor)  // フルスクリーン
customHeightSheetPresenter.present(.picker) // カスタム高さシート
alertPresenter.present(.error("エラー"))   // アラート
tabPresenter.select(.search)               // タブ切り替え
splitViewPresenter.select(.inbox)          // サイドバー選択

実装例

完全な実装例を参照:

  • TodoExample - Navigation, Sheet, Alert, TabView, FullScreenCover, CustomHeightSheet
  • MailExample - 3カラムNavigationSplitView

要件

  • iOS 17.0+ / macOS 14.0+
  • Swift 6.0+

ライセンス

MIT License - 詳細は LICENSE を参照

サポート

問題や機能リクエストは GitHub Issues

© 2026 Kyoichi Taniguchi. All rights reserved.