Repository navigation
v1.0.6 - ネストプレゼンテーション & ファイル構造改善
·
57 commits
to main
since this release
Release v1.0.6 - ネストプレゼンテーション & ファイル構造改善
🎉 新機能
ネストプレゼンテーション対応
シート内からシート、フルスクリーンカバー、カスタム高さシートを開く機能を追加
// シート内でさらにシートを開く
struct AddTodoSheet: View {
@Environment(.sheet(AppSheet.self, context: .sheet)) private var sheetPresenter
var body: some View {
Button("カテゴリ選択") {
sheetPresenter.present(.categoryPicker(onSelect: { category in
selectedCategory = category
}))
}
}
}
// シート定義にPresenterを適用
enum AppSheet: Sheetable {
case addTodo
case categoryPicker(onSelect: (Category) -> Void)
var body: some View {
switch self {
case .addTodo:
NavigationStack {
AddTodoSheet()
}
.sheetPresenter(for: AppSheet.self) // ネストに必要
case .categoryPicker(let onSelect):
CategoryPickerSheet(onSelect: onSelect)
}
}
}PresentationContext
.navigation と .sheet でプレゼンテーション階層を明確に区別
// 通常のプレゼンテーション(デフォルト)
@Environment(.sheet(AppSheet.self)) private var sheetPresenter
@Environment(.fullScreenCover(Cover.self)) private var coverPresenter
@Environment(.customHeightSheet(Sheet.self)) private var customPresenter
// ネストプレゼンテーション(シート内から)
@Environment(.sheet(AppSheet.self, context: .sheet)) private var sheetPresenter
@Environment(.fullScreenCover(Cover.self, context: .sheet)) private var coverPresenter
@Environment(.customHeightSheet(Sheet.self, context: .sheet)) private var customPresenter
@Environment(.alert(AppAlert.self, context: .sheet)) private var alertPresenterクロージャベースのデータ受け渡し
Bindingを使わずに型安全にデータを受け渡し
// ❌ Bindingは使えない(Hashableではない)
case categoryPicker(category: Binding<Category>)
// ✅ クロージャで型安全に実装
case categoryPicker(onSelect: (Category) -> Void)
// 使用例
sheetPresenter.present(.categoryPicker(onSelect: { category in
self.selectedCategory = category
}))🏗️ ファイル構造改善
大規模リファクタリング
1000行超のコードを機能別に分割し、保守性を大幅向上
分割前(5ファイル):
Specifiers.swift(146行)GenericEnvironmentKeys.swift(135行)StaticMemberLookup.swift(364行)PresentationModifiers.swift(255行)EnvironmentSubscripts.swift(100行)
分割後(61ファイル):
UIRouting/
├── Alert/ (6ファイル) - Alert関連コンポーネント
│ ├── AlertPresenter.swift
│ ├── AlertSpecifier.swift
│ ├── AlertEnvironmentKey.swift
│ ├── AlertEnvironmentKeys.swift
│ ├── AlertEnvironmentSubscript.swift
│ └── AlertModifiers.swift
├── Common/ (2ファイル) - 共通インフラ
│ ├── PresentationContext.swift
│ └── RoutingModifier.swift
├── CustomHeightSheet/ (6ファイル)
├── FullScreenCover/ (6ファイル)
├── Router/ (6ファイル)
├── Sheet/ (6ファイル)
├── SplitView/ (9ファイル)
└── Tab/ (7ファイル)
設計原則の適用
コロケーション(Colocation):
関連ファイルを機能ドメインごとにグループ化
- 例:
Sheet/に SheetPresenter, SheetSpecifier, SheetEnvironmentKey, SheetModifiers などを配置
単一責任(Single Responsibility):
各ファイルが1つの明確な責務のみを持つ
- Specifier: 環境値の識別
- EnvironmentKey: 静的メンバールックアップ
- Subscript: 環境値のストレージ
- Modifiers: プレゼンテーション設定
保守性向上:
- 変更時の影響範囲が明確
- 公開APIと内部実装の分離
- ナビゲーションと理解が容易
📁 ディレクトリ変更
削除
Internal/- 不要になったため削除Environment/- ファイルを適切な場所に再配置
新規作成
Common/- 共通コンポーネント(PresentationContext, RoutingModifier)
再配置
PresentationContext.swift: Internal/ → Common/RoutingModifier.swift: Environment/ → Common/
📚 実装例
TodoExample更新
CategoryPickerSheetの実装を追加し、ネストプレゼンテーションの使い方を実演
// CategoryPickerSheetViewWithClosure.swift
struct CategoryPickerSheetViewWithClosure: View {
let onSelect: (Todo.Category) -> Void
@Environment(\.dismiss) private var dismiss
var body: some View {
List(Todo.Category.allCases, id: \.self) { category in
Button(action: {
onSelect(category)
dismiss()
}) {
HStack {
Text(category.emoji)
Text(category.name)
}
}
}
.navigationTitle("カテゴリ選択")
}
}🔧 技術詳細
型安全なネスト
Mirror-based Hashable実装(v1.0.5)により、クロージャを含むenumでもコンパイルエラーなし
コンテキスト分離
Environment値の階層管理で、親プレゼンテーションと子プレゼンテーションを独立して管理
Modifierの配置
ネストプレゼンテーションには親となるビューに .sheetPresenter() などを適用
📦 互換性
- iOS 17.0+ / macOS 14.0+
- Swift 6.0+
- 後方互換性: 既存コードはそのまま動作(新機能は追加のみ)
✅ 影響
- ✅ 公開API: 後方互換性あり(新機能はadditive)
- ✅ ビルド: すべて正常にコンパイル
- ✅ テスト: すべてのテストがパス
- ✅ ドキュメント: 最新の構造を反映
🔗 リンク
- 完全ドキュメント
- TodoExample - ネストプレゼンテーションの実装例
- CHANGELOG
📝 変更履歴
詳細は CHANGELOG.md を参照