rshankras/persistence-setup
Generates SwiftData or CoreData persistence layer with optional iCloud sync. Use when user wants to add local storage, data persistence, or cloud sync.
npx skills add https://github.com/rshankras/claude-code-apple-skills --skill persistence-setup
Generates a production-ready persistence layer using SwiftData (iOS 17+) or CoreData with optional iCloud (CloudKit) sync.
Before generating, ALWAYS check:
# Check deployment target
cat Package.swift | grep -i "platform"
# Or check project.pbxproj
# Find existing persistence implementations
rg -l "ModelContainer|NSPersistentContainer|@Model|@Entity" --type swift
# Check for existing SwiftData models
rg "@Model" --type swift | head -5
# Check for CoreData stack
rg "NSManagedObjectContext|NSPersistentStore" --type swift | head -5
# Check existing entitlements for iCloud
cat *.entitlements 2>/dev/null | grep -i "icloud"
Use SwiftData if:
Use CoreData if:
If existing persistence found:
Ask user via AskUserQuestion:
Always generate:
Sources/Persistence/
├── PersistenceController.swift # Container setup
├── Repository.swift # Repository protocol
└── SwiftDataRepository.swift # Concrete implementation
If example model requested:
Sources/Persistence/Models/
└── Item.swift # Sample @Model
If iCloud enabled:
Sources/Persistence/CloudSync/
├── CloudKitConfiguration.swift # Container identifier
└── SyncStatus.swift # Sync monitoring
Read templates from this skill:
templates/PersistenceController.swifttemplates/Repository.swifttemplates/SwiftDataRepository.swifttemplates/ExampleModel.swifttemplates/CloudKitConfiguration.swift (if iCloud)templates/SyncStatus.swift (if iCloud)Adapt templates to match:
Basic Integration:
@main
struct MyApp: App {
let container = PersistenceController.shared.container
var body: some Scene {
WindowGroup {
ContentView()
.modelContainer(container)
}
}
}
With iCloud sync:
@main
struct MyApp: App {
let container = PersistenceController.shared.container
var body: some Scene {
WindowGroup {
ContentView()
.modelContainer(container)
.environment(\.syncStatus, SyncStatus.shared)
}
}
}
iCloud.com.yourcompany.yourapp<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.yourcompany.yourapp</string>
</array>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>
</array>
protocol Repository<T>: Sendable {
associatedtype T: PersistentModel
func fetch(predicate: Predicate<T>?, sortBy: [SortDescriptor<T>]) async throws -> [T]
func insert(_ item: T) async throws
func delete(_ item: T) async throws
func save() async throws
}
@Model
final class Item {
var title: String
var timestamp: Date
var isCompleted: Bool
init(title: String, timestamp: Date = .now, isCompleted: Bool = false) {
self.title = title
self.timestamp = timestamp
self.isCompleted = isCompleted
}
}
let container = try ModelContainer(
for: Item.self,
configurations: ModelConfiguration(
cloudKitDatabase: .private("iCloud.com.yourcompany.yourapp")
)
)
After generation, verify:
@Model
final class Project {
var name: String
@Relationship(deleteRule: .cascade) var items: [Item]
init(name: String, items: [Item] = []) {
self.name = name
self.items = items
}
}
// Update container
let container = try ModelContainer(for: Item.self, Project.self)
let descriptor = FetchDescriptor<Item>(
predicate: #Predicate { $0.isCompleted == false },
sortBy: [SortDescriptor(\.timestamp, order: .reverse)]
)
let items = try modelContext.fetch(descriptor)
// SwiftData handles lightweight migrations automatically
// For complex migrations, use VersionedSchema
enum ItemSchemaV1: VersionedSchema {
static var versionIdentifier = Schema.Version(1, 0, 0)
static var models: [any PersistentModel.Type] { [Item.self] }
}
modelContainer modifier is on root viewsave() is called after modificationscloudKitDatabase: .automatic for shared containersnetworking-layer - For remote API data alongside local cachesettings-screen - Often uses @AppStorage (simpler persistence)Take rshankras/persistence-setup from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.