Skip to content

v1.0.6 - ネストプレゼンテーション & ファイル構造改善

Choose a tag to compare

@no-problem-kyoichi no-problem-kyoichi released this 06 Nov 00:35
· 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)
  • ✅ ビルド: すべて正常にコンパイル
  • ✅ テスト: すべてのテストがパス
  • ✅ ドキュメント: 最新の構造を反映

🔗 リンク

📝 変更履歴

詳細は CHANGELOG.md を参照