--- name: swift-data description: SwiftData persistence and data-layer architecture for iOS 26 / Swift 6.2 clinic modular MVVM-C apps. Use when writing, reviewing, or refactoring @Model entities, repository implementations, stale-while-revalidate reads, optimistic queued writes, sync/retry behavior, and SwiftUI integration that keeps SwiftData types inside Data-only boundaries. --- # SwiftData Best Practices — Modular MVVM-C Data Layer Comprehensive data modeling, persistence, sync architecture, and error handling guide for SwiftData aligned with the clinic modular MVVM-C stack. ## Architecture Alignment This skill enforces the same modular architecture mandated by `swift-ui-architect`: ``` ┌───────────────────────────────────────────────────────────────┐ │ Feature modules: View + ViewModel, no SwiftData imports │ ├───────────────────────────────────────────────────────────────┤ │ Domain: models + repository/coordinator/error protocols │ ├───────────────────────────────────────────────────────────────┤ │ Data: @Model entities, SwiftData stores, repository impls, │ │ remote clients, retry executor, sync queue, conflict handling │ └───────────────────────────────────────────────────────────────┘ ``` **Key principle:** SwiftData types (`@Model`, `ModelContext`, `@Query`, `FetchDescriptor`) live in Data-only implementation code. Feature Views/ViewModels work with Domain types and protocol dependencies. ## Clinic Architecture Contract (iOS 26 / Swift 6.2) All guidance in this skill assumes the clinic modular MVVM-C architecture: - Feature modules import `Domain` + `DesignSystem` only (never `Data`, never sibling features) - App target is the convergence point and owns `DependencyContainer`, concrete coordinators, and Route Shell wiring - `Domain` stays pure Swift and defines models plus repository, `*Coordinating`, `ErrorRouting`, and `AppError` contracts - `Data` owns SwiftData/network/sync/retry/background I/O and implements Domain protocols - Read/write flow defaults to stale-while-revalidate reads and optimistic queued writes - ViewModels call repository protocols directly (no default use-case/interactor layer) ## When to Apply Reference these guidelines when: - Defining @Model entity classes and mapping them to domain structs - Setting up ModelContainer and ModelContext in the Data layer - Implementing repository protocols backed by SwiftData - Writing stale-while-revalidate repository reads (`AsyncStream`) - Implementing optimistic writes plus queued sync operations - Configuring entity relationships (one-to-many, inverse) - Fetching from APIs and persisting to SwiftData via sync coordinators - Handling save failures, corrupt stores, and migration errors - Routing AppError traits to centralized error UI infrastructure - Building preview infrastructure with sample data - Planning schema migrations for app updates ## Workflow Use this workflow when designing or refactoring a SwiftData-backed feature: 1. Domain design: define domain structs (`Trip`, `Friend`) with validation/computed rules (see `model-domain-mapping`, `state-business-logic-placement`) 2. Entity design: define `@Model` entity classes with mapping methods (see `model-*`, `model-domain-mapping`) 3. Repository protocol: define in Domain layer, implement with SwiftData in Data layer (see `persist-repository-wrapper`) 4. Container wiring: configure `ModelContainer` once at the app boundary with error recovery (see `persist-container-setup`, `persist-container-error-recovery`) 5. Dependency injection: inject repository protocols via @Environment (see `state-dependency-injection`) 6. ViewModel: create @Observable ViewModel that delegates directly to repository protocols (see `state-query-vs-viewmodel`) 7. CRUD flows: route all insert/delete/update through ViewModel -> Repository (see `crud-*`) 8. Sync architecture: queue writes, execute via sync coordinator with retry policy (see `sync-*`) 9. Relationships: model to-many relationships as arrays; define delete rules (see `rel-*`) 10. Previews: create in-memory containers and sample data for fast iteration (see `preview-*`) 11. Schema evolution: plan migrations with versioned schemas (see `schema-*`) ## Troubleshooting - Data not persisting -> `persist-model-macro`, `persist-container-setup`, `persist-autosave`, `schema-configuration` - List not updating after background import -> `query-background-refresh`, `persist-model-actor` - List not updating (same-context) -> `query-property-wrapper`, `state-wrapper-views` - Duplicates from API sync -> `schema-unique-attributes`, `sync-conflict-resolution` - App crashes on launch after model change -> `schema-migration-recovery`, `persist-container-error-recovery` - Save failures silently losing data -> `crud-save-error-handling` - Stale data from network -> `sync-offline-first`, `sync-fetch-persist` - Widget/extension can't see data -> `persist-app-group`, `schema-configuration` - Choosing architecture pattern for data views -> `state-query-vs-viewmodel`, `persist-repository-wrapper` ## Rule Categories by Priority | Priority | Category | Impact | Prefix | |----------|----------|--------|--------| | 1 | Data Modeling | CRITICAL | `model-` | | 2 | Persistence Setup | CRITICAL | `persist-` | | 3 | Querying & Filtering | HIGH | `query-` | | 4 | CRUD Operations | HIGH | `crud-` | | 5 | Sync & Networking | HIGH | `sync-` | | 6 | Relationships | MEDIUM-HIGH | `rel-` | | 7 | SwiftUI State Flow | MEDIUM-HIGH | `state-` | | 8 | Schema & Migration | MEDIUM-HIGH | `schema-` | | 9 | Sample Data & Previews | MEDIUM | `preview-` | ## Quick Reference ### 1. Data Modeling (CRITICAL) - [`model-domain-mapping`](references/model-domain-mapping.md) - Map @Model entities to domain structs across Domain/Data boundaries - [`model-custom-types`](references/model-custom-types.md) - Use custom types over parallel arrays - [`model-class-for-persistence`](references/model-class-for-persistence.md) - Use classes for SwiftData entity types - [`model-identifiable`](references/model-identifiable.md) - Conform entities to Identifiable with UUID - [`model-initializer`](references/model-initializer.md) - Provide custom initializers for entity classes - [`model-computed-properties`](references/model-computed-properties.md) - Use computed properties for derived data - [`model-defaults`](references/model-defaults.md) - Provide sensible default values for entity properties - [`model-transient`](references/model-transient.md) - Mark non-persistent properties with @Transient - [`model-external-storage`](references/model-external-storage.md) - Use external storage for large binary data ### 2. Persistence Setup (CRITICAL) - [`persist-repository-wrapper`](references/persist-repository-wrapper.md) - Wrap SwiftData behind Domain repository protocols - [`persist-model-macro`](references/persist-model-macro.md) - Apply @Model macro to all persistent types - [`persist-container-setup`](references/persist-container-setup.md) - Configure ModelContainer at the App level - [`persist-container-error-recovery`](references/persist-container-error-recovery.md) - Handle ModelContainer creation failure with store recovery - [`persist-context-environment`](references/persist-context-environment.md) - Access ModelContext via @Environment (Data layer) - [`persist-autosave`](references/persist-autosave.md) - Enable autosave for manually created contexts - [`persist-enumerate-batch`](references/persist-enumerate-batch.md) - Use ModelContext.enumerate for large traversals - [`persist-in-memory-config`](references/persist-in-memory-config.md) - Use in-memory configuration for tests and previews - [`persist-app-group`](references/persist-app-group.md) - Use App Groups for shared data storage - [`persist-model-actor`](references/persist-model-actor.md) - Use @ModelActor for background SwiftData work - [`persist-identifier-transfer`](references/persist-identifier-transfer.md) - Pass PersistentIdentifier across actors ### 3. Querying & Filtering (HIGH) - [`query-property-wrapper`](references/query-property-wrapper.md) - Use @Query for declarative data fetching (Data layer) - [`query-background-refresh`](references/query-background-refresh.md) - Force view refresh after background context inserts - [`query-sort-descriptors`](references/query-sort-descriptors.md) - Apply sort descriptors to @Query - [`query-predicates`](references/query-predicates.md) - Use #Predicate for type-safe filtering - [`query-dynamic-init`](references/query-dynamic-init.md) - Use custom view initializers for dynamic queries - [`query-fetch-descriptor`](references/query-fetch-descriptor.md) - Use FetchDescriptor outside SwiftUI views - [`query-fetch-tuning`](references/query-fetch-tuning.md) - Tune FetchDescriptor paging and pending-change behavior - [`query-localized-search`](references/query-localized-search.md) - Use localizedStandardContains for search - [`query-expression`](references/query-expression.md) - Use #Expression for reusable predicate components (iOS 18+) ### 4. CRUD Operations (HIGH) - [`crud-insert-context`](references/crud-insert-context.md) - Insert models via repository implementations - [`crud-delete-indexset`](references/crud-delete-indexset.md) - Delete via repository with IndexSet from onDelete - [`crud-sheet-creation`](references/crud-sheet-creation.md) - Use sheets for focused data creation via ViewModel - [`crud-cancel-delete`](references/crud-cancel-delete.md) - Avoid orphaned records by persisting only on save - [`crud-undo-cancel`](references/crud-undo-cancel.md) - Enable undo and use it to cancel edits - [`crud-edit-button`](references/crud-edit-button.md) - Provide EditButton for list management - [`crud-dismiss-save`](references/crud-dismiss-save.md) - Dismiss modal after ViewModel save completes - [`crud-save-error-handling`](references/crud-save-error-handling.md) - Handle repository save failures with user feedback ### 5. Sync & Networking (HIGH) - [`sync-fetch-persist`](references/sync-fetch-persist.md) - Use injected sync services to fetch and persist API data - [`sync-offline-first`](references/sync-offline-first.md) - Design offline-first architecture with repository reads and background sync - [`sync-conflict-resolution`](references/sync-conflict-resolution.md) - Implement conflict resolution for bidirectional sync ### 6. Relationships (MEDIUM-HIGH) - [`rel-optional-single`](references/rel-optional-single.md) - Use optionals for optional relationships - [`rel-array-many`](references/rel-array-many.md) - Use arrays for one-to-many relationships - [`rel-inverse-auto`](references/rel-inverse-auto.md) - Rely on SwiftData automatic inverse maintenance - [`rel-delete-rules`](references/rel-delete-rules.md) - Configure cascade delete rules for owned relationships - [`rel-explicit-sort`](references/rel-explicit-sort.md) - Sort relationship arrays explicitly ### 7. SwiftUI State Flow (MEDIUM-HIGH) - [`state-query-vs-viewmodel`](references/state-query-vs-viewmodel.md) - Route all data access through @Observable ViewModels - [`state-business-logic-placement`](references/state-business-logic-placement.md) - Place business logic in domain value types and repository-backed ViewModels - [`state-dependency-injection`](references/state-dependency-injection.md) - Inject repository protocols via @Environment - [`state-bindable`](references/state-bindable.md) - Use @Bindable for two-way model binding - [`state-local-state`](references/state-local-state.md) - Use @State for view-local transient data - [`state-wrapper-views`](references/state-wrapper-views.md) - Extract wrapper views for dynamic query state ### 8. Schema & Migration (MEDIUM-HIGH) - [`schema-define-all-types`](references/schema-define-all-types.md) - Define schema with all model types - [`schema-unique-attributes`](references/schema-unique-attributes.md) - Use @Attribute(.unique) for natural keys - [`schema-unique-macro`](references/schema-unique-macro.md) - Use #Unique for compound uniqueness (iOS 18+) - [`schema-index`](references/schema-index.md) - Use #Index for hot predicates and sorts (iOS 18+) - [`schema-migration-plan`](references/schema-migration-plan.md) - Plan migrations before changing models - [`schema-migration-recovery`](references/schema-migration-recovery.md) - Plan migration recovery for schema changes - [`schema-configuration`](references/schema-configuration.md) - Customize storage with ModelConfiguration ### 9. Sample Data & Previews (MEDIUM) - [`preview-sample-singleton`](references/preview-sample-singleton.md) - Create a SampleData singleton for previews - [`preview-in-memory`](references/preview-in-memory.md) - Use in-memory containers for preview isolation - [`preview-static-data`](references/preview-static-data.md) - Define static sample data on model types - [`preview-main-actor`](references/preview-main-actor.md) - Annotate SampleData with @MainActor ## How to Use Read individual reference files for detailed explanations and code examples: - [Section definitions](references/_sections.md) - Category structure and impact levels - [Rule template](assets/templates/_template.md) - Template for adding new rules ## Reference Files | File | Description | |------|-------------| | [references/_sections.md](references/_sections.md) | Category definitions and ordering | | [assets/templates/_template.md](assets/templates/_template.md) | Template for new rules | | [metadata.json](metadata.json) | Version and reference information |