SwiftUIアプリ向けの型安全なナビゲーションライブラリ。@Routeマクロによる宣言的なルーティングと、ディープリンク、ミドルウェア、Swift 6 Concurrencyをサポートします。
import Navigator
import SwiftUI
@Route
enum AppRoute {
case home
case profile(userId: String)
case article(id: String, section: String?)
}
struct ContentView: View {
@State private var navigator = Navigator<AppRoute>()
var body: some View {
NavigationStack(path: $navigator.path) {
HomeView()
.navigationDestination(for: AppRoute.self) { route in
switch route {
case .home:
HomeView()
case .profile(let userId):
ProfileView(userId: userId)
case .article(let id, let section):
ArticleView(id: id, section: section)
}
}
}
.environment(navigator)
}
}
struct HomeView: View {
@Environment(Navigator<AppRoute>.self) private var navigator
var body: some View {
VStack(spacing: 20) {
Button("プロフィールを見る") {
navigator.push(.profile(userId: "123"))
}
Button("戻る") {
navigator.pop()
}
}
}
}シンプルなenumと@Routeマクロでルートを定義。マクロがURL解析、パス生成、Hashable準拠を自動生成します。
@Route
enum AppRoute {
case home
case profile(userId: String)
case article(id: String, section: String?)
}
// 自動生成されるプロパティ:
AppRoute.home.path // → "home"
AppRoute.profile(userId: "123").url(scheme: "myapp")
// → myapp://profile?userId=123- URL処理のボイラープレート不要
- 型安全なパラメータ
- オプショナルパラメータ対応
Navigatorはスタックナビゲーションとタブ管理を分離。Navigatorがpush/pop操作を、TabNavigatorがタブ選択を管理します。
// スタックナビゲーション
@State private var navigator = Navigator<AppRoute>()
// タブ管理
@State private var tabNavigator = TabNavigator<AppTab>(defaultTab: .home)
// それぞれが専用のAPIを持つ
navigator.push(.profile(userId: "123"))
navigator.pop()
tabNavigator.switchTo(.settings)- 各コンポーネントが単一の責任
- クリーンで焦点を絞ったAPI
- テストと理解が容易
すべてのルートが自動的にディープリンクをサポート。URLをルートに解析し、ルートからURLを生成—すべて型安全。
// URL生成
let url = AppRoute.profile(userId: "123").url(scheme: "myapp")
// → myapp://profile?userId=123
// URL解析
let route = AppRoute.parse(from: url)
// → Optional(.profile(userId: "123"))
// SwiftUIでの処理
NavigationStack(path: $navigator.path) {
HomeView()
.navigationDestination(for: AppRoute.self) { ... }
}
.onOpenURL { url in
if let route = AppRoute.parse(from: url) {
navigator.push(route)
}
}- 自動URL生成
- 型安全なURL解析
- SwiftUI統合済み
NavigatorはSwift 6の厳密なConcurrencyに対応。すべてのナビゲーション操作が@MainActor分離され、コンパイル時にスレッド安全性を保証します。
@MainActor
@Observable
public final class Navigator<Route: Navigatable> {
public var path: [Route] = []
public func push(_ route: Route) {
path.append(route)
// ...
}
}- コンパイル時のスレッド安全性
- データ競合なし
defaultIsolation(MainActor.self)と連携
enum AppTab: Hashable {
case home, profile, settings
}
struct ContentView: View {
@State private var homeNav = Navigator<AppRoute>()
@State private var profileNav = Navigator<AppRoute>()
@State private var tabNav = TabNavigator<AppTab>(defaultTab: .home)
var body: some View {
TabView(selection: $tabNav.selectedTab) {
NavigationStack(path: $homeNav.path) {
HomeView()
.navigationDestination(for: AppRoute.self) { route in
routeView(for: route)
}
}
.tabItem { Label("ホーム", systemImage: "house") }
.tag(AppTab.home)
.environment(homeNav)
NavigationStack(path: $profileNav.path) {
ProfileView()
.navigationDestination(for: AppRoute.self) { route in
routeView(for: route)
}
}
.tabItem { Label("プロフィール", systemImage: "person") }
.tag(AppTab.profile)
.environment(profileNav)
}
.environment(tabNav)
}
}ログ、アナリティクス、バリデーションなどの横断的関心事を追加:
struct LoggingMiddleware: NavigationMiddleware {
typealias Route = AppRoute
func onNavigate(to route: AppRoute) {
print("ナビゲート: \(route.path)")
}
func onPop(route: AppRoute) {
print("ポップ: \(route.path)")
}
func onPopToRoot(removedRoutes: [AppRoute]) {
print("ルートへポップ: \(removedRoutes.count)件削除")
}
}
// ミドルウェアを追加
let token = navigator.addMiddleware(LoggingMiddleware())
// 後でトークンを使って削除
navigator.removeMiddleware(token: token)タブナビゲーションもミドルウェアをサポート:
struct TabLoggingMiddleware: TabNavigationMiddleware {
typealias Tab = AppTab
func onTabSwitch(fromTab: AppTab, toTab: AppTab) {
print("タブ切替: \(fromTab) → \(toTab)")
}
}
tabNav.addMiddleware(TabLoggingMiddleware())| メソッド | 説明 |
|---|---|
push(_:) |
ルートをスタックにプッシュ |
pop() |
現在のルートをポップ |
pop(count:) |
複数のルートをポップ |
pop(to:) |
指定ルートまでポップ |
popToRoot() |
ナビゲーションスタックをクリア |
addMiddleware(_:) |
ミドルウェアを追加(トークンを返す) |
removeMiddleware(token:) |
トークンでミドルウェアを削除 |
removeMiddleware(_:) |
型でミドルウェアを削除 |
clearMiddlewares() |
全ミドルウェアを削除 |
| メソッド | 説明 |
|---|---|
switchTo(_:) |
タブを切り替え |
isSelected(_:) |
タブが選択中か確認 |
addMiddleware(_:) |
ミドルウェアを追加(トークンを返す) |
removeMiddleware(token:) |
トークンでミドルウェアを削除 |
removeMiddleware(_:) |
型でミドルウェアを削除 |
clearMiddlewares() |
全ミドルウェアを削除 |
Package.swiftにNavigatorを追加:
dependencies: [
.package(url: "https://github.com/ViewFeature/Navigator.git", from: "0.1.0")
],
targets: [
.target(
name: "YourApp",
dependencies: [
.product(name: "Navigator", package: "Navigator")
],
swiftSettings: [
.defaultIsolation(MainActor.self) // 推奨
]
)
]- File → Add Package Dependencies を選択
- URLを入力:
https://github.com/ViewFeature/Navigator.git - バージョン:
0.1.0以降を選択
推奨: ターゲットの Build Settings → Other Swift Flags に -default-isolation MainActor を追加。
- iOS 18.0+ / macOS 15.0+ / watchOS 11.0+ / tvOS 18.0+
- Swift 6.2+
- Xcode 16.2+
コントリビュートを歓迎します!
プルリクエストを送信する前に、Contributing Guideをご確認ください。質問やアイデアがあれば、Discussionを開始してください。
- Issue報告 - バグ報告と機能リクエスト
- Discussions - 質問とアイデアの共有
Navigatorは以下のライブラリとコミュニティからインスピレーションを受けています:
- SwiftUI NavigationStack - AppleのネイティブナビゲーションAPI
- The Composable Architecture - Swiftでのナビゲーションパターン
- swift-syntax - Swiftマクロ基盤
NavigatorはMITライセンスの下で配布されています。詳細はLICENSEファイルをご覧ください。