swift-data
SKILL.md
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+DesignSystemonly (neverData, never sibling features) - App target is the convergence point and owns
DependencyContainer, concrete coordinators, and Route Shell wiring Domainstays pure Swift and defines models plus repository,*Coordinating,ErrorRouting, andAppErrorcontractsDataowns 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:
- Domain design: define domain structs (
Trip,Friend) with validation/computed rules (seemodel-domain-mapping,state-business-logic-placement) - Entity design: define
@Modelentity classes with mapping methods (seemodel-*,model-domain-mapping) - Repository protocol: define in Domain layer, implement with SwiftData in Data layer (see
persist-repository-wrapper) - Container wiring: configure
ModelContaineronce at the app boundary with error recovery (seepersist-container-setup,persist-container-error-recovery) - Dependency injection: inject repository protocols via @Environment (see
state-dependency-injection) - ViewModel: create @Observable ViewModel that delegates directly to repository protocols (see
state-query-vs-viewmodel) - CRUD flows: route all insert/delete/update through ViewModel -> Repository (see
crud-*) - Sync architecture: queue writes, execute via sync coordinator with retry policy (see
sync-*) - Relationships: model to-many relationships as arrays; define delete rules (see
rel-*) - Previews: create in-memory containers and sample data for fast iteration (see
preview-*) - 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- Map @Model entities to domain structs across Domain/Data boundariesmodel-custom-types- Use custom types over parallel arraysmodel-class-for-persistence- Use classes for SwiftData entity typesmodel-identifiable- Conform entities to Identifiable with UUIDmodel-initializer- Provide custom initializers for entity classesmodel-computed-properties- Use computed properties for derived datamodel-defaults- Provide sensible default values for entity propertiesmodel-transient- Mark non-persistent properties with @Transientmodel-external-storage- Use external storage for large binary data
2. Persistence Setup (CRITICAL)
persist-repository-wrapper- Wrap SwiftData behind Domain repository protocolspersist-model-macro- Apply @Model macro to all persistent typespersist-container-setup- Configure ModelContainer at the App levelpersist-container-error-recovery- Handle ModelContainer creation failure with store recoverypersist-context-environment- Access ModelContext via @Environment (Data layer)persist-autosave- Enable autosave for manually created contextspersist-enumerate-batch- Use ModelContext.enumerate for large traversalspersist-in-memory-config- Use in-memory configuration for tests and previewspersist-app-group- Use App Groups for shared data storagepersist-model-actor- Use @ModelActor for background SwiftData workpersist-identifier-transfer- Pass PersistentIdentifier across actors
3. Querying & Filtering (HIGH)
query-property-wrapper- Use @Query for declarative data fetching (Data layer)query-background-refresh- Force view refresh after background context insertsquery-sort-descriptors- Apply sort descriptors to @Queryquery-predicates- Use #Predicate for type-safe filteringquery-dynamic-init- Use custom view initializers for dynamic queriesquery-fetch-descriptor- Use FetchDescriptor outside SwiftUI viewsquery-fetch-tuning- Tune FetchDescriptor paging and pending-change behaviorquery-localized-search- Use localizedStandardContains for searchquery-expression- Use #Expression for reusable predicate components (iOS 18+)
4. CRUD Operations (HIGH)
crud-insert-context- Insert models via repository implementationscrud-delete-indexset- Delete via repository with IndexSet from onDeletecrud-sheet-creation- Use sheets for focused data creation via ViewModelcrud-cancel-delete- Avoid orphaned records by persisting only on savecrud-undo-cancel- Enable undo and use it to cancel editscrud-edit-button- Provide EditButton for list managementcrud-dismiss-save- Dismiss modal after ViewModel save completescrud-save-error-handling- Handle repository save failures with user feedback
5. Sync & Networking (HIGH)
sync-fetch-persist- Use injected sync services to fetch and persist API datasync-offline-first- Design offline-first architecture with repository reads and background syncsync-conflict-resolution- Implement conflict resolution for bidirectional sync
6. Relationships (MEDIUM-HIGH)
rel-optional-single- Use optionals for optional relationshipsrel-array-many- Use arrays for one-to-many relationshipsrel-inverse-auto- Rely on SwiftData automatic inverse maintenancerel-delete-rules- Configure cascade delete rules for owned relationshipsrel-explicit-sort- Sort relationship arrays explicitly
7. SwiftUI State Flow (MEDIUM-HIGH)
state-query-vs-viewmodel- Route all data access through @Observable ViewModelsstate-business-logic-placement- Place business logic in domain value types and repository-backed ViewModelsstate-dependency-injection- Inject repository protocols via @Environmentstate-bindable- Use @Bindable for two-way model bindingstate-local-state- Use @State for view-local transient datastate-wrapper-views- Extract wrapper views for dynamic query state
8. Schema & Migration (MEDIUM-HIGH)
schema-define-all-types- Define schema with all model typesschema-unique-attributes- Use @Attribute(.unique) for natural keysschema-unique-macro- Use #Unique for compound uniqueness (iOS 18+)schema-index- Use #Index for hot predicates and sorts (iOS 18+)schema-migration-plan- Plan migrations before changing modelsschema-migration-recovery- Plan migration recovery for schema changesschema-configuration- Customize storage with ModelConfiguration
9. Sample Data & Previews (MEDIUM)
preview-sample-singleton- Create a SampleData singleton for previewspreview-in-memory- Use in-memory containers for preview isolationpreview-static-data- Define static sample data on model typespreview-main-actor- Annotate SampleData with @MainActor
How to Use
Read individual reference files for detailed explanations and code examples:
- Section definitions - Category structure and impact levels
- Rule template - Template for adding new rules
Reference Files
| File | Description |
|---|---|
| references/_sections.md | Category definitions and ordering |
| assets/templates/_template.md | Template for new rules |
| metadata.json | Version and reference information |
Weekly Installs
37
Repository
pproenca/dot-skillsGitHub Stars
70
First Seen
Feb 10, 2026
Security Audits
Installed on
codex33
github-copilot32
gemini-cli32
opencode31
kimi-cli31
amp30