Working with SwiftData
1. Defining Models with @Model Macro
| Property | Type | Description |
|---|---|---|
| @Model | macro |
Defines a model class that can be used with SwiftData |
| @Attribute | macro |
Defines a property as an attribute in the model |
| @Relationship | macro |
Defines a property as a relationship to another model |
Example: Basic Model Definition
import SwiftData
@Model
final class Book {
var title: String
var author: String
var publishedDate: Date
init(title: String, author: String, publishedDate: Date) {
self.title = title
self.author = author
self.publishedDate = publishedDate
}
}
Example: Model with Relationships
import SwiftData
@Model
final class Author {
var name: String
@Relationship(inverse: \.author)
var books: [Book]
init(name: String) {
self.name = name
}
}
@Model
final class Book {
var title: String
@Relationship(inverse: \.books)
var author: Author?
init(title: String) {
self.title = title
}
}
2. Creating Model Container
SwiftData uses a ModelContainer to manage the storage and retrieval of model objects. The container is initialized with a ModelSchema that defines the structure of your data models.
| Property | Type | Description |
|---|---|---|
| ModelContainer | struct |
Represents the storage container for SwiftData models. |
| ModelSchema | struct |
Defines the schema of your data models. |
Example: Creating a Model Container
import SwiftData
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: [MyModel.self])
}
}
Note: The
modelContainer(for:) method is used to create a model container for the specified models. It automatically handles schema creation and migration.
3. Using ModelContext for Operations
| Operation | Description | Code Example |
|---|---|---|
| insert(_:) | Inserts a new entity into the context | |
| delete(_:) | Deletes an entity from the context | modelContext.delete(person) |
| save() | Saves all changes made in the context to the persistent store | try modelContext.save() |
| fetch(_:) | Fetches entities matching a specified predicate | |
| performFetch(_:) | Performs a fetch operation asynchronously | |
Note: ModelContext is a key part of SwiftData's API for managing data operations. It provides methods to insert, delete, and fetch entities.
4. Querying Data with @Query
| Property | Description | Usage |
|---|---|---|
| @Query | Declares a property that is automatically populated with query results from SwiftData | @Query(variant: .count) var count: Int |
Example: Basic @Query usage
import SwiftData
@Model
struct Book {
var title: String
var author: String
}
struct ContentView: View {
@Query(
.from(Book.self)
.where(\.author == "J.K. Rowling")
) var books: [Book]
var body: some View {
List(books, id: \.title) { book in
Text(book.title)
}
}
}
Example: @Query with sorting and limit
struct BookListView: View {
@Query(
.from(Book.self)
.sorted(by: \.title, order: .ascending)
.limit(10)
) var books: [Book]
var body: some View {
ForEach(books) { book in
Text(book.title)
}
}
}
Example: @Query with count
struct BookCountView: View {
@Query(
variant: .count,
.from(Book.self)
.where(\.author == "George Orwell")
) var bookCount: Int
var body: some View {
Text("Books by George Orwell: \(bookCount)")
}
}
Note: @Query is a property wrapper that automatically updates the view when data changes in SwiftData.
Warning: @Query does not support complex nested queries or subqueries. For advanced filtering, use
fetch with SwiftData’s FetchDescriptor.
5. Inserting and Deleting Models
| Operation | Description | Code Example |
|---|---|---|
| Inserting a Model | Use insert method on a ModelContext to add a new model instance. |
|
| Deleting a Model | Use delete method on a ModelContext to remove a model instance. |
context.delete(book) |
Note: Changes to models must be committed using
save on the ModelContext to persist changes to disk.6. Working with Relationships
| Relationship Type | Description | Code Example | Notes |
|---|---|---|---|
| To-One | Represents a single related object. | @Relationship(inverse: \.user) var profile: Profile? |
Use optional for nullable relationships |
| To-Many | Represents a collection of related objects. | @Relationship(inverse: \.user) var posts: [Post] |
Defaults to empty array if no values |
| Inverse Relationship | Defines the reverse side of a relationship. | @Relationship(inverse: \.user) var profile: Profile? |
Must be defined on both sides for consistency |
Example: Defining a To-Many Relationship
import SwiftData
@Model
final class User {
var name: String
@Relationship(inverse: \.user) var posts: [Post] = []
init(name: String) {
self.name = name
}
}
@Model
final class Post {
var title: String
@Relationship(inverse: \.posts) var user: User?
init(title: String) {
self.title = title
}
}
Example: Fetching Related Objects
let context = try! ViewContext()
let user = try! context.fetch(FetchDescriptor<User>())
let posts = user.posts // Access related posts directly
Note: SwiftData automatically manages the lifecycle of related objects when they are fetched or modified.
Warning: Avoid circular references in relationships to prevent infinite loops during data loading.
7. Using Sort Descriptors
| Property | Description | Usage |
|---|---|---|
| SortDescriptor | Defines how to sort data in SwiftData | SortDescriptor(\.name) |
| keyPath | Property to sort by | SortDescriptor(\.age) |
| ascending | Sort direction (default true) | SortDescriptor(\.name, ascending: false) |
Example: Sorting with SortDescriptor
import SwiftData
struct Person: PersistentModel {
var name: String
var age: Int
}
let sortDescriptors = [
SortDescriptor(\.age, ascending: false),
SortDescriptor(\.name)
]
let results = try context.fetch(FetchDescriptor<Person>(sortDescriptors: sortDescriptors))
8. Filtering with Predicates
| Property | Type | Description |
|---|---|---|
| predicate | NSPredicate? |
Defines the condition to filter data |
Example: Filtering with Predicate
import SwiftData
@Model
final class Task {
var title: String
var isCompleted: Bool
init(title: String, isCompleted: Bool = false) {
self.title = title
self.isCompleted = isCompleted
}
}
struct TaskListView: View {
@Query(
predicate: #Predicate<Task> { task in
task.isCompleted == false
}
) var tasks: [Task]
var body: some View {
List(tasks, id: \.self) { task in
Text(task.title)
}
}
}
Note: Predicates in SwiftData are defined using the
#Predicate macro for type-safe filtering.
| Predicate Operator | Description | Example |
|---|---|---|
== |
Equal to | task.title == "Buy milk" |
!= |
Not equal to | task.isCompleted != true |
< |
Less than | task.priority < 3 |
> |
Greater than | task.dueDate > Date() |
contains |
Contains substring | task.title.contains("grocery") |
beginsWith |
Begins with string | task.title.beginsWith("Important") |
endsWith |
Ends with string | task.title.endsWith("urgent") |
Example: Complex Predicate
let predicate = #Predicate<Task> { task in
task.isCompleted == false &&
task.title.contains("grocery") &&
task.dueDate <= Date().addingTimeInterval(86400) // within 24 hours
}
9. Migrating from Core Data
Core Data vs SwiftData Migration Considerations
- Core Data is a mature framework with extensive documentation and community support, while SwiftData is newer and has less third-party tooling.
- SwiftData uses a more modern, Swift-native approach with less boilerplate but lacks some of Core Data's advanced features like NSFetchedResultsController.
- Migration strategies differ significantly; SwiftData provides built-in migration support, whereas Core Data requires more manual handling.
| Feature | Core Data | SwiftData |
|---|---|---|
| Migration Strategy | Manual implementation using NSMigrationManager or lightweight migration | Built-in support for lightweight and custom migrations |
| Data Model | Uses .xcdatamodeld files with XML-based schema definition | Uses Swift structs and classes annotated with @Model |
| Performance | Optimized for large datasets with background processing | Designed for modern Swift performance, but may not handle very large datasets as efficiently |
| API | Complex API with many classes (NSManagedObject, NSManagedObjectContext, etc.) | Simpler API focused on Swift-native patterns |
Example: Migrating a Simple Core Data Model to SwiftData
// Core Data Model
class User: NSManagedObject {
@NSManaged var name: String
@NSManaged var email: String
}
// SwiftData Model
import SwiftData
@Model
final class User {
var name: String
var email: String
init(name: String, email: String) {
self.name = name
self.email = email
}
}
Note: When migrating to SwiftData, ensure that all entities are properly annotated with
@Model and that relationships are defined using Swift-native references rather than Core Data's NSManagedObject instances.
Warning: SwiftData does not support all Core Data features such as NSFetchedResultsController or complex predicate operations. Review your app's usage of these features before migration.
10. Understanding SwiftData vs Core Data
SwiftData vs Core Data
- SwiftData is a modern, declarative framework introduced in iOS 17, designed for easier data persistence with Swift-first APIs. Core Data is a mature, robust framework that has been part of iOS/macOS for over 15 years, offering extensive features and backward compatibility.
- SwiftData uses Swift-native syntax and is built on top of Core Data, simplifying development for new projects. Core Data offers more complex but powerful features like NSFetchedResultsController, custom NSManagedObject subclasses, and extensive mapping capabilities.
- SwiftData is designed for modern Swift projects and supports SwiftUI integration out-of-the-box. Core Data requires more boilerplate and is more complex to integrate with SwiftUI.
- SwiftData provides a simpler API for basic CRUD operations, while Core Data offers more granular control over data fetching and transformation.
- SwiftData is not yet suitable for complex, large-scale applications or apps requiring extensive customization. Core Data is well-suited for large, complex applications with extensive data models and custom logic.
| Feature | SwiftData | Core Data |
|---|---|---|
| API Style | Declarative, Swift-native | Traditional, Objective-C-based |
| Data Model | Uses Swift structs and classes with @Model | Uses NSManagedObject subclasses |
| SwiftUI Integration | Native support with @FetchRequest | Requires manual integration |
| Migration Handling | Automatic migration in simple cases | Manual migration handling with NSMigrationManager |
| Performance | Optimized for modern Swift apps | Highly optimized, but more complex to tune |
| Platform Support | iOS 17+, macOS 14+ | iOS 5+, macOS 10.4+ |