install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/pproenca/dot-skills/swift-data" ~/.claude/skills/comeonoliver-skillshub-swift-data && rm -rf "$T"
manifest:
skills/pproenca/dot-skills/swift-data/SKILL.mdsource content
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
only (neverDesignSystem
, never sibling features)Data - App target is the convergence point and owns
, concrete coordinators, and Route Shell wiringDependencyContainer
stays pure Swift and defines models plus repository,Domain
,*Coordinating
, andErrorRouting
contractsAppError
owns SwiftData/network/sync/retry/background I/O and implements Domain protocolsData- 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
) with validation/computed rules (seeFriend
,model-domain-mapping
)state-business-logic-placement - Entity design: define
entity classes with mapping methods (see@Model
,model-*
)model-domain-mapping - Repository protocol: define in Domain layer, implement with SwiftData in Data layer (see
)persist-repository-wrapper - Container wiring: configure
once at the app boundary with error recovery (seeModelContainer
,persist-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-autosaveschema-configuration - List not updating after background import ->
,query-background-refreshpersist-model-actor - List not updating (same-context) ->
,query-property-wrapperstate-wrapper-views - Duplicates from API sync ->
,schema-unique-attributessync-conflict-resolution - App crashes on launch after model change ->
,schema-migration-recoverypersist-container-error-recovery - Save failures silently losing data ->
crud-save-error-handling - Stale data from network ->
,sync-offline-firstsync-fetch-persist - Widget/extension can't see data ->
,persist-app-groupschema-configuration - Choosing architecture pattern for data views ->
,state-query-vs-viewmodelpersist-repository-wrapper
Rule Categories by Priority
| Priority | Category | Impact | Prefix |
|---|---|---|---|
| 1 | Data Modeling | CRITICAL | |
| 2 | Persistence Setup | CRITICAL | |
| 3 | Querying & Filtering | HIGH | |
| 4 | CRUD Operations | HIGH | |
| 5 | Sync & Networking | HIGH | |
| 6 | Relationships | MEDIUM-HIGH | |
| 7 | SwiftUI State Flow | MEDIUM-HIGH | |
| 8 | Schema & Migration | MEDIUM-HIGH | |
| 9 | Sample Data & Previews | MEDIUM | |
Quick Reference
1. Data Modeling (CRITICAL)
- Map @Model entities to domain structs across Domain/Data boundariesmodel-domain-mapping
- Use custom types over parallel arraysmodel-custom-types
- Use classes for SwiftData entity typesmodel-class-for-persistence
- Conform entities to Identifiable with UUIDmodel-identifiable
- Provide custom initializers for entity classesmodel-initializer
- Use computed properties for derived datamodel-computed-properties
- Provide sensible default values for entity propertiesmodel-defaults
- Mark non-persistent properties with @Transientmodel-transient
- Use external storage for large binary datamodel-external-storage
2. Persistence Setup (CRITICAL)
- Wrap SwiftData behind Domain repository protocolspersist-repository-wrapper
- Apply @Model macro to all persistent typespersist-model-macro
- Configure ModelContainer at the App levelpersist-container-setup
- Handle ModelContainer creation failure with store recoverypersist-container-error-recovery
- Access ModelContext via @Environment (Data layer)persist-context-environment
- Enable autosave for manually created contextspersist-autosave
- Use ModelContext.enumerate for large traversalspersist-enumerate-batch
- Use in-memory configuration for tests and previewspersist-in-memory-config
- Use App Groups for shared data storagepersist-app-group
- Use @ModelActor for background SwiftData workpersist-model-actor
- Pass PersistentIdentifier across actorspersist-identifier-transfer
3. Querying & Filtering (HIGH)
- Use @Query for declarative data fetching (Data layer)query-property-wrapper
- Force view refresh after background context insertsquery-background-refresh
- Apply sort descriptors to @Queryquery-sort-descriptors
- Use #Predicate for type-safe filteringquery-predicates
- Use custom view initializers for dynamic queriesquery-dynamic-init
- Use FetchDescriptor outside SwiftUI viewsquery-fetch-descriptor
- Tune FetchDescriptor paging and pending-change behaviorquery-fetch-tuning
- Use localizedStandardContains for searchquery-localized-search
- Use #Expression for reusable predicate components (iOS 18+)query-expression
4. CRUD Operations (HIGH)
- Insert models via repository implementationscrud-insert-context
- Delete via repository with IndexSet from onDeletecrud-delete-indexset
- Use sheets for focused data creation via ViewModelcrud-sheet-creation
- Avoid orphaned records by persisting only on savecrud-cancel-delete
- Enable undo and use it to cancel editscrud-undo-cancel
- Provide EditButton for list managementcrud-edit-button
- Dismiss modal after ViewModel save completescrud-dismiss-save
- Handle repository save failures with user feedbackcrud-save-error-handling
5. Sync & Networking (HIGH)
- Use injected sync services to fetch and persist API datasync-fetch-persist
- Design offline-first architecture with repository reads and background syncsync-offline-first
- Implement conflict resolution for bidirectional syncsync-conflict-resolution
6. Relationships (MEDIUM-HIGH)
- Use optionals for optional relationshipsrel-optional-single
- Use arrays for one-to-many relationshipsrel-array-many
- Rely on SwiftData automatic inverse maintenancerel-inverse-auto
- Configure cascade delete rules for owned relationshipsrel-delete-rules
- Sort relationship arrays explicitlyrel-explicit-sort
7. SwiftUI State Flow (MEDIUM-HIGH)
- Route all data access through @Observable ViewModelsstate-query-vs-viewmodel
- Place business logic in domain value types and repository-backed ViewModelsstate-business-logic-placement
- Inject repository protocols via @Environmentstate-dependency-injection
- Use @Bindable for two-way model bindingstate-bindable
- Use @State for view-local transient datastate-local-state
- Extract wrapper views for dynamic query statestate-wrapper-views
8. Schema & Migration (MEDIUM-HIGH)
- Define schema with all model typesschema-define-all-types
- Use @Attribute(.unique) for natural keysschema-unique-attributes
- Use #Unique for compound uniqueness (iOS 18+)schema-unique-macro
- Use #Index for hot predicates and sorts (iOS 18+)schema-index
- Plan migrations before changing modelsschema-migration-plan
- Plan migration recovery for schema changesschema-migration-recovery
- Customize storage with ModelConfigurationschema-configuration
9. Sample Data & Previews (MEDIUM)
- Create a SampleData singleton for previewspreview-sample-singleton
- Use in-memory containers for preview isolationpreview-in-memory
- Define static sample data on model typespreview-static-data
- Annotate SampleData with @MainActorpreview-main-actor
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 |