SwiftData is Apple's modern framework for data persistence, replacing Core Data with a simpler Swift-native API.
Getting Started with SwiftData
Basic Setup
swift
import SwiftData
import SwiftUI
@main
struct MyApp: App {
let modelContainer: ModelContainer
init() {
do {
modelContainer = try ModelContainer(
for: Item.self
)
} catch {
fatalError("Failed to create ModelContainer")
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(modelContainer)
}
}Defining Models
Simple Model
swift
import SwiftData
import Foundation
@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
}
}Relationships
swift
@Model
final class Author {
var name: String
@Relationship(deleteRule: .cascade) var books: [Book]
init(name: String) {
self.name = name
self.books = []
}
}
@Model
final class Book {
var title: String
var publicationYear: Int
@Relationship(inverse: \Author.books) var author: Author?
init(title: String, publicationYear: Int) {
self.title = title
self.publicationYear = publicationYear
}
}Advanced Model Configuration
swift
@Model
final class Product {
var name: String
var price: Double
var quantity: Int
// Unique identifier
@Attribute(.unique) var uuid: UUID
// Indexed for faster queries
@Attribute(.unique) var sku: String
// Exclude from persistent storage
@Transient var temporaryValue: String = ""
init(name: String, price: Double, quantity: Int) {
self.name = name
self.price = price
self.quantity = quantity
self.uuid = UUID()
self.sku = "SKU-\(UUID().uuidString.prefix(8))"
}
}CRUD Operations
Create
swift
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var items: [Item]
var body: some View {
VStack {
List {
ForEach(items) { item in
Text(item.title)
}
}
Button("Add Item") {
addItem()
}
}
}
private func addItem() {
let newItem = Item(title: "New Item")
modelContext.insert(newItem)
}
}Read
swift
struct ContentView: View {
// Basic query
@Query private var items: [Item]
// Filtered query
@Query(filter: #Predicate<Item> { item in
item.isCompleted == false
}) var incompleteItems: [Item]
// Sorted query
@Query(sort: \Item.timestamp) var sortedItems: [Item]
// Complex query
@Query(
filter: #Predicate<Item> { item in
item.title.contains("important") &&
item.isCompleted == false
},
sort: \Item.timestamp
) var importantItems: [Item]
var body: some View {
List {
ForEach(items) { item in
Text(item.title)
}
}
}
}Update
swift
struct EditView: View {
@Environment(\.modelContext) private var modelContext
var item: Item
var body: some View {
Form {
TextField("Title", text: $item.title)
Toggle("Completed", isOn: $item.isCompleted)
}
.onDisappear {
// Changes are automatically saved
try? modelContext.save()
}
}
}Delete
swift
struct ItemRow: View {
@Environment(\.modelContext) private var modelContext
var item: Item
var body: some View {
HStack {
Text(item.title)
Spacer()
Button("Delete") {
modelContext.delete(item)
}
}
}
}Advanced Queries
Programmatic Queries
swift
class ViewModel: ObservableObject {
@Published var items: [Item] = []
private var modelContext: ModelContext
init(modelContext: ModelContext) {
self.modelContext = modelContext
}
func searchItems(_ searchText: String) {
let predicate = #Predicate<Item> { item in
item.title.contains(searchText)
}
let descriptor = FetchDescriptor<Item>(
predicate: predicate,
sortBy: [SortDescriptor(\Item.timestamp)]
)
do {
items = try modelContext.fetch(descriptor)
} catch {
print("Search failed: \(error)")
}
}
func findItemsCreatedAfter(_ date: Date) {
let predicate = #Predicate<Item> { item in
item.timestamp > date
}
let descriptor = FetchDescriptor<Item>(predicate: predicate)
do {
items = try modelContext.fetch(descriptor)
} catch {
print("Query failed: \(error)")
}
}
}Data Migration
swift
enum AppSchema: VersionedSchema {
static var versionIdentifier = Schema.Version(1, 0, 0)
static var models: [any PersistentModel.Type] {
[Item.self]
}
static var schema: Schema {
Schema(versionIdentifier) {
// Define your models here
}
}
}
enum AppMigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] {
[AppSchema.self]
}
static var stages: [MigrationStage] {
[]
}
static var migrationTypes: [any Migration.Type] {
[]
}
}SwiftData with SwiftUI
Real-time Updates
swift
struct LiveView: View {
@Query private var items: [Item]
var body: some View {
List {
ForEach(items) { item in
HStack {
Text(item.title)
Spacer()
if item.isCompleted {
Image(systemName: "checkmark")
}
}
}
}
// Automatically updates when data changes
}
}Search and Filter
swift
struct SearchableView: View {
@Query private var allItems: [Item]
@State private var searchText = ""
var filteredItems: [Item] {
guard !searchText.isEmpty else { return allItems }
return allItems.filter { $0.title.localizedStandardContains(searchText) }
}
var body: some View {
List {
ForEach(filteredItems) { item in
Text(item.title)
}
}
.searchable(text: $searchText)
}
}Best Practices
- 1Use models: Define clear data models
- 2Lazy loading: Use queries for large datasets
- 3Relationships: Define clear relationships
- 4Indexing: Add indexes for frequently queried properties
- 5Validation: Validate data before saving
- 6Migration: Plan for data model changes
- 7Testing: Test persistence layer thoroughly
SwiftData provides a modern, Swift-native approach to data persistence!