Back to OSS
swift-ui-routing
SwiftUI向けの型安全で宣言的なルーティングライブラリ
Swift
swiftuiroutingnavigation
UIRouting
SwiftUI向けの型安全なルーティングライブラリ
特徴
// 画面遷移
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 へ