
swiftdata
Implement, review, or improve data persistence using SwiftData. Use when defining @Model classes with @Attribute, @Relationship, @Transient, Unique, or Index; when querying with @Query, Predicate, FetchDescriptor, or SortDescriptor; when configuring ModelContainer and ModelContext for SwiftUI or background work with @ModelActor; when planning schema migrations with VersionedSchema and SchemaMigrationPlan; when setting up CloudKit sync with ModelConfiguration; or when coexisting with or migrating from Core Data.
- Installs
- 4.2K
- Last 24h
- +15
- Trending rank
- #6954
- Security
- Pass
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swiftdata- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads swiftdata automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
Security audits
Independent scans of the skill's instructions and bundled files.
SKILL.md
The instructions your agent reads when the skill is triggered.
SwiftData
Persist, query, and manage structured data in iOS 26+ apps using SwiftData with Swift 6.3.
Contents
- Model Definition
- ModelContainer Setup
- CloudKit Sync
- CRUD Operations
@Query in SwiftUI- #Predicate
- FetchDescriptor
- Schema Versioning and Migration
- Core Data Coexistence Boundary
- Concurrency (
@ModelActor) - SwiftUI Integration
- Common Mistakes
- Review Checklist
- References
Model Definition
Apply @Model to a class (not struct). It synthesizes PersistentModel
conformance. Model instances remain context/actor-bound; pass their
PersistentIdentifier, not the instance, across actors.
@Model
class Trip {
var name: String
var destination: String
var startDate: Date
var endDate: Date
var isFavorite: Bool = false
@Attribute(.externalStorage) var imageData: Data?
@Relationship(deleteRule: .cascade, inverse: \LivingAccommodation.trip)
var accommodation: LivingAccommodation?
@Transient var isSelected: Bool = false // Always provide default
init(name: String, destination: String, startDate: Date, endDate: Date) {
self.name = name; self.destination = destination
self.startDate = startDate; self.endDate = endDate
}
}
@Attribute options: .externalStorage, .unique, .spotlight, .allowsCloudEncryption, .preserveValueOnDeletion, .ephemeral, .transformable(by:). Rename: @Attribute(originalName: "old_name").
@Relationship: deleteRule: .cascade/.nullify(default)/.deny/.noAction. Specify inverse: for reliable behavior. Unidirectional (iOS 18+): inverse: nil.
#Unique (iOS 18+): #Unique<Person>([\.firstName, \.lastName]) -- compound uniqueness.
Inheritance (iOS 26+): @Model class BusinessTrip: Trip { var company: String }.
Supported types: Bool, Int/UInt variants, Float, Double, String, Date, Data, URL, UUID, Decimal, Array, Dictionary, Set, Codable enums, Codable structs and other compatible Codable value types, and relationships to @Model classes.
ModelContainer Setup
// Basic
let container = try ModelContainer(for: Trip.self, LivingAccommodation.self)
// Configured
let config = ModelConfiguration("Store", isStoredInMemoryOnly: false,
groupContainer: .identifier("group.com.example.app"),
cloudKitDatabase: .private("iCloud.com.example.app"))
let container = try ModelContainer(for: Trip.self, configurations: config)
// With migration plan
let container = try ModelContainer(for: SchemaV2.Trip.self,
migrationPlan: TripMigrationPlan.self)
// In-memory (previews/tests)
let container = try ModelContainer(for: Trip.self,
configurations: ModelConfiguration(isStoredInMemoryOnly: true))
CloudKit Sync
ModelConfiguration(..., cloudKitDatabase:) opts a SwiftData store into
automatic CloudKit sync, but app entitlements still gate sync.
For any SwiftData CloudKit setup or schema-review task, include a separate Capabilities verdict before schema findings:
- Capabilities: Xcode target has the iCloud capability with CloudKit enabled
and the intended container selected, plus Background Modes > Remote
notifications. Without these entitlements, automatic sync is not fully
configured even if
cloudKitDatabaseis set. - Schema compatibility: no
@Attribute(.unique)or#Unique; relationships are optional, have explicit inverses where needed, and avoid.deny; largeDatauses@Attribute(.externalStorage). - Scalar attributes: do not make every scalar optional just for CloudKit. Keep required scalars nonoptional when initializers, defaults, or migrations provide valid values.
- Schema rollout: initialize the development schema only in nonproduction builds, verify it in CloudKit Dashboard, promote before release, and treat production changes as additive only.
CRUD Operations
For destructive batches and migrations, first run the exact predicate or version hop against a disposable copy and record affected identifiers/counts. Execute with explicit transaction/save semantics, refetch, and verify values, relationships, counts, and invariants. On failure, fix the predicate/schema and restore a pristine fixture before retrying; never blindly replay a destructive operation.
// CREATE
let trip = Trip(name: "Summer", destination: "Paris", startDate: .now, endDate: .now + 86400*7)
modelContext.insert(trip)
try modelContext.save() // or rely on autosave
// READ
let trips = try modelContext.fetch(FetchDescriptor<Trip>(
predicate: #Predicate { $0.destination == "Paris" },
sortBy: [SortDescriptor(\.startDate)]))
// UPDATE -- modify properties directly; autosave handles persistence
trip.destination = "Rome"
// DELETE
modelContext.delete(trip)
try modelContext.delete(model: Trip.self, where: #Predicate { $0.isFavorite == false })
// TRANSACTION (atomic)
try modelContext.transaction {
modelContext.insert(trip); trip.isFavorite = true
}
@Query in SwiftUI
struct TripListView: View {
@Query(filter: #Predicate<Trip> { $0.isFavorite == true },
sort: \.startDate, order: .reverse)
private var favorites: [Trip]
var body: some View { List(favorites) { trip in Text(trip.name) } }
}
// Dynamic query via init
struct SearchView: View {
@Query private var trips: [Trip]
init(search: String) {
_trips = Query(filter: #Predicate<Trip> { trip in
search.isEmpty || trip.name.localizedStandardContains(search)
}, sort: [SortDescriptor(\.name)])
}
var body: some View { List(trips) { trip in Text(trip.name) } }
}
// FetchDescriptor query
struct RecentView: View {
static var desc: FetchDescriptor<Trip> {
var d = FetchDescriptor<Trip>(sortBy: [SortDescriptor(\.startDate)])
d.fetchLimit = 5; return d
}
@Query(RecentView.desc) private var recent: [Trip]
var body: some View { List(recent) { trip in Text(trip.name) } }
}
#Predicate
#Predicate<Trip> { $0.destination.localizedStandardContains("paris") } // String
let now = Date()
#Predicate<Trip> { $0.startDate > now } // Date
#Predicate<Trip> { $0.isFavorite && $0.destination != "Unknown" } // Compound
#Predicate<Trip> { $0.accommodation?.name != nil } // Optional
#Predicate<Trip> { $0.tags.contains { $0.name == "adventure" } } // Collection
Supported: ==, !=, <, <=, >, >=, &&, ||, !, contains(), allSatisfy(), filter(), starts(with:), localizedStandardContains(), caseInsensitiveCompare(), arithmetic, conditional expressions, optional chaining and binding, nil coalescing, type casting. Avoid: loops, nested declarations, mutations, and arbitrary unsupported method calls.
FetchDescriptor
var d = FetchDescriptor<Trip>(predicate: ..., sortBy: [...])
d.fetchLimit = 20; d.fetchOffset = 0
d.includePendingChanges = true
d.propertiesToFetch = [\.name, \.startDate]
d.relationshipKeyPathsForPrefetching = [\.accommodation]
let trips = try modelContext.fetch(d)
let count = try modelContext.fetchCount(d)
let ids = try modelContext.fetchIdentifiers(d)
try modelContext.enumerate(d, batchSize: 1000) { trip in trip.isProcessed = true }
Schema Versioning and Migration
enum SchemaV1: VersionedSchema {
static var versionIdentifier = Schema.Version(1, 0, 0)
static var models: [any PersistentModel.Type] { [Trip.self] }
@Model class Trip { var name: String; init(name: String) { self.name = name } }
}
enum SchemaV2: VersionedSchema {
static var versionIdentifier = Schema.Version(2, 0, 0)
static var models: [any PersistentModel.Type] { [Trip.self] }
@Model class Trip {
var name: String; var startDate: Date? // New property
init(name: String) { self.name = name }
}
}
enum TripMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] { [SchemaV1.self, SchemaV2.self] }
static var stages: [MigrationStage] { [migrateV1toV2] }
static let migrateV1toV2 = MigrationStage.lightweight(
fromVersion: SchemaV1.self, toVersion: SchemaV2.self)
}
// Custom migration for data transformation
static let migrateV2toV3 = MigrationStage.custom(
fromVersion: SchemaV2.self, toVersion: SchemaV3.self,
willMigrate: nil,
didMigrate: { context in
let trips = try context.fetch(FetchDescriptor<SchemaV3.Trip>())
for trip in trips { trip.displayName = trip.name.capitalized }
try context.save()
})
Lightweight handles: adding optional/defaulted properties, renaming (originalName), removing properties, adding model types.
Verify the stage list covers every supported version hop, then migrate a fresh
copy of each old store and assert post-migration data before release.
Core Data Coexistence Boundary
Use this skill when the work is to run SwiftData alongside an existing Core
Data store or migrate screens from Core Data to SwiftData over time. Keep pure
Core Data stack setup, NSManagedObjectContext, NSFetchRequest, and batch
Core Data operations in the sibling core-data skill.
For coexistence, give boundary guidance before detailed migration advice:
- Point SwiftData and Core Data at the same SQLite store URL.
- Match Core Data entity names, property names, types, and relationship shapes
in the SwiftData
@Modeldefinitions. - Use
@Attribute(originalName:)for SwiftData properties whose persisted Core Data names differ from the Swift names. - Do not write the same entity from both stacks at the same time; assign one stack as the writer for each entity during migration.
Concurrency (@ModelActor)
@ModelActor
actor DataHandler {
func importTrips(_ records: [TripRecord]) throws {
for r in records {
modelContext.insert(Trip(name: r.name, destination: r.dest,
startDate: r.start, endDate: r.end))
}
try modelContext.save() // Always save explicitly in @ModelActor
}
func process(tripID: PersistentIdentifier) throws {
guard let trip = self[tripID, as: Trip.self] else { return }
trip.isProcessed = true; try modelContext.save()
}
}
let handler = DataHandler(modelContainer: container)
try await handler.importTrips(records)
Rules: ModelContainer is Sendable. ModelContext is NOT -- use on its creating actor. Pass PersistentIdentifier (Sendable) across boundaries. Never pass @Model objects across actors.
SwiftUI Integration
@main
struct MyApp: App {
var body: some Scene {
WindowGroup { ContentView() }
.modelContainer(for: [Trip.self, LivingAccommodation.self])
}
}
struct DetailView: View {
@Environment(\.modelContext) private var modelContext
let trip: Trip
var body: some View {
Text(trip.name)
Button("Delete") { modelContext.delete(trip) }
}
}
#Preview {
let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try! ModelContainer(for: Trip.self, configurations: config)
container.mainContext.insert(Trip(name: "Preview", destination: "London",
startDate: .now, endDate: .now + 86400))
return TripListView().modelContainer(container)
}
Common Mistakes
1. @Model on struct -- Use class. @Model requires reference semantics.
2. @Transient without default -- Always provide default: @Transient var x: Bool = false.
3. Missing .modelContainer -- @Query returns empty without a container on the view hierarchy.
4. Passing model objects across actors:
// WRONG: await handler.process(trip: trip)
// CORRECT: await handler.process(tripID: trip.persistentModelID)
5. ModelContext on wrong actor:
// WRONG: Task.detached { context.fetch(...) }
// CORRECT: Use @ModelActor for background work
6. Unsupported #Predicate expressions:
// WRONG: #Predicate<Trip> { $0.name.uppercased() == "PARIS" }
// CORRECT: #Predicate<Trip> { $0.name.localizedStandardContains("paris") }
7. Flow control in #Predicate:
// WRONG: #Predicate<Trip> { for tag in $0.tags { ... } }
// CORRECT: #Predicate<Trip> { $0.tags.contains { $0.name == "x" } }
8. No save in @ModelActor -- Always call try modelContext.save() explicitly.
9. ObservableObject with @Model -- Never use ObservableObject/@Published. @Model generates Observable. Use @Query in views.
10. Non-optional relationship without default:
// WRONG: var accommodation: LivingAccommodation // crashes on reconstitution
// CORRECT: var accommodation: LivingAccommodation?
11. Cascade without inverse -- Specify inverse: for reliable cascade delete behavior.
12. DispatchQueue for background data work:
// WRONG: DispatchQueue.global().async { ModelContext(container).fetch(...) }
// CORRECT: @ModelActor actor Handler { func fetch() throws { ... } }
Review Checklist
- Every
@Modelis a class with a designated initializer - All
@Transientproperties have default values - Relationships specify
deleteRuleandinverse -
.modelContainerattached at scene/root view level -
@Queryused for reactive data display in SwiftUI -
#Predicateuses only supported operators - Background work uses
@ModelActor -
PersistentIdentifierused across actor boundaries - Schema changes have
VersionedSchema+SchemaMigrationPlan - Large data uses
@Attribute(.externalStorage) - CloudKit models avoid uniqueness, use optional relationships, avoid
.deny, and do not blanket-optionalize scalars - CloudKit sync has iCloud + CloudKit, Remote notifications, and production schema rollout checked
- Explicit
save()in@ModelActormethods - Previews use
ModelConfiguration(isStoredInMemoryOnly: true) -
@Modelclasses accessed from SwiftUI views are on@MainActorvia@ModelActoror MainActor isolation
References
- references/swiftdata-advanced.md — custom data stores, history tracking, CloudKit, composite attributes, model inheritance, undo/redo, performance
- references/swiftdata-queries.md —
@Queryvariants, FetchDescriptor deep dive, sectioned queries, dynamic queries, background fetch - references/core-data-coexistence.md — Core Data + SwiftData coexistence and migration boundaries
- references/predicate-pitfalls.md — #Predicate runtime crashes, unsupported expressions, safe patterns
- references/indexing.md — #Index macro, compound indexes, when to index, migration
FAQ
What does the swiftdata skill do?
Implement, review, or improve data persistence using SwiftData. Use when defining @Model classes with @Attribute, @Relationship, @Transient, Unique, or Index; when querying with @Query, Predicate, FetchDescriptor, or SortDescriptor; when configuring ModelContainer and ModelContext for SwiftUI or background work with @ModelActor; when planning schema migrations with VersionedSchema and SchemaMigrationPlan; when setting up CloudKit sync with ModelConfiguration; or when coexisting with or migrating from Core Data.
How do I install swiftdata?
Run npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swiftdata in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/swiftdata/ (personal) or .claude/skills/swiftdata/ (project).
Which AI agents can use swiftdata?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is swiftdata safe to install?
Security partners have reviewed it: Gen Agent Trust Hub passed; Socket passed; Snyk passed; Runlayer passed; ZeroLeaks passed. As with any skill, read the SKILL.md and bundled scripts before installing.
Who maintains swiftdata?
It is published by dpearson2699 in dpearson2699/swift-ios-skills. It has 4,150 recorded installs.
More from dpearson2699/swift-ios-skills
swiftui-animation
dpearson2699/swift-ios-skills
Implement, diagnose, or review SwiftUI motion using explicit and scoped implicit animations, springs, transitions, PhaseAnimator, KeyframeAnimator, matched geometry or navigation zoom, SF Symbol effects, and custom Animation types. Use when views should animate on state changes, insertion, removal, navigation, or multi-step choreography, or when motion must respect Reduce Motion and Swift concurrency.
ios-accessibility
dpearson2699/swift-ios-skills
Build and audit SwiftUI, UIKit, and AppKit accessibility for VoiceOver, Voice Control, Switch Control, Full Keyboard Access, Dynamic Type, focus restoration, labels/traits/actions, traversal, custom rotors, NSAccessibility, XCTest checks, adaptive system preferences, and App Store accessibility declarations. Use when implementing accessible UI, fixing an accessibility audit, testing assistive-technology behavior, or substantiating App Store Accessibility Nutrition Labels.
swiftui-performance
dpearson2699/swift-ios-skills
Profile, diagnose, and remediate SwiftUI runtime performance using code review, Instruments, and repeatable measurements. Use when a SwiftUI screen renders slowly, scrolling or animations hitch, view bodies update excessively, list identity churns, layout work spikes, or broad Observation dependencies raise CPU cost. Covers evidence-based triage, SwiftUI Instruments lanes, lazy-container guardrails, state lifetime, and before/after verification.
swiftui-patterns
dpearson2699/swift-ios-skills
Builds and reviews SwiftUI views with modern MV architecture, state, composition, isolated previews, and migration guidance. Covers @Observable ownership, @State/@Bindable/@Environment wiring, view decomposition, ViewModifiers, environment values, .task loading, iOS 26+ handoffs, Writing Tools, clipboard availability, and performance. Use when structuring SwiftUI state, managing @Observable, composing views, previewing meaningful UI states, or correcting SwiftUI patterns.
app-store-review
dpearson2699/swift-ios-skills
Audits App Store submission readiness and rejection risk across current review guidelines, PrivacyInfo.xcprivacy and required-reason APIs, privacy labels, ATT, StoreKit payments, metadata, entitlements, widgets, and Live Activities. Use when preparing a submission, responding to rejection, reconciling privacy evidence, or separating upload blockers from cleanup.
ios-networking
dpearson2699/swift-ios-skills
Build, review, or improve networking code in iOS/macOS apps using URLSession with async/await, structured concurrency, and modern Swift patterns. Use when working with REST APIs, downloading files, uploading data, WebSocket connections, pagination, retry logic, request middleware, caching, background transfers, or network reachability monitoring. Also use when handling HTTP requests, API clients, network error handling, or data fetching in Swift apps.
Popular mobile skills
design-mobile-apps
designed-by-ai/skills
Design mobile app screens with Sleek, edit Sleek projects, and implement their designs in React Native or HTML.
ios-design
uizze.sh
Design and review iOS UI/UX for iPhone apps in SwiftUI, UIKit, or React Native. Use for mobile app design, screen redesigns, navigation flows, typography, forms, keyboard and safe-area layout, and accessibility checks. Apply Apple Human Interface Guidelines and consult real product references through the optional Uizze connection.
vercel-react-native-skills
vercel-labs/agent-skills
React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.
animate-expo
emilkowalski/skills
Build animations in React Native and Expo, making the decisions in the order that determines whether they feel right — should it animate, which thread it runs on, which properties, spring or timing, how the gesture hands off, how it degrades. Writes the implementation with Reanimated, Gesture Handler, Expo Router and expo-haptics. Use when animating anything in an Expo app, adding gestures, sheets, screen transitions, press feedback or haptics, or fixing motion that stutters on device. For web animation use animate.
xcode-project-setup
firebase/agent-skills
Safely modifies Xcode projects (.pbxproj) to add Swift Packages and link files. Use this skill whenever an iOS project needs dependencies installed (e.g. Firebase, Alamofire).
firebase-remote-config-basics
firebase/agent-skills
Manages Firebase Remote Config templates, feature flags, loading strategies, and SDKs (Android, iOS). Use when downloading/deploying remoteconfig JSON templates, managing version history/feature flags, setting in-app defaults, fetchAndActivate(), real-time listeners, or SDK setup. Don't use for Firebase Hosting, Auth, Firestore, Data Connect, Crashlytics, or App Hosting.