There are at least three ways to delete data from a SwiftData store. Here’s a quick recap.
Last updated: Apr 2, 2024
Delete A Single Item
If you only want to delete a single object, call the delete method on a model context passing the model object you want to delete:
context.delete(country)
The method doesn’t throw or return a value. If you’re not using autosave the SwiftData documentation recommends you save the context after deletion. A few observations that I’ve not found well documented:
- After the delete operation completes, but before you save (or autosave), the
isDeleted
property istrue
for the deleted model object, and the deleted object is present in the context’sdeletedModelsArray
. - After you save the context, the
isDeleted
property on the deleted model object is reset to false, thedeletedModelsArray
is empty. - After you save the context, the deleted model is no longer attached to the context and its
modelContext
property isnil
.
In other words, after deleting but before saving:
context.delete(country)
// isDeleted property is true
XCTAssertTrue(country.isDeleted)
// country is in the deletedModelsArray of the context
XCTAssertFalse(context.deletedModelsArray.isEmpty)
// The country is still part of the context
XCTAssertNotNil(country.modelContext)
After saving the context:
try context.save()
// isDeleted property is false
XCTAssertFalse(country.isDeleted)
// deletedModelsArray of the context is empty
XCTAssertTrue(context.deletedModelsArray.isEmpty)
// The country has no model context
XCTAssertNil(country.modelContext)
This means it’s safe to have a SwiftUI view showing a deleted object. That wasn’t the case during the early iOS 17 betas when SwiftData behaved more like Core Data and set the non-scalar properties of a deleted model object to nil.
It’s a little confusing but if you want to detect a deleted object it’s better to check the modelContext
property rather than isDeleted
.
Delete Where…
I missed it when SwiftData was first released but if you want to delete all items matching a predicate use the ModelContext
method delete(model:where:includeSubclasses:)
. The predicate defaults to nil
which will delete all items of that model type.
For example, to delete all Country
objects from my store:
// Delete *ALL* Country items
try viewContext.delete(model: Country.self)
Or using a predicate to delete all visited countries:
// Delete visited countries
try viewContext.delete(model: Country.self, where: #Predicate {
$0.visited
})
Using the CoreData SQLDebug logging we can see the SQL statement that this generates:
CoreData: sql: DELETE FROM ZCOUNTRY WHERE Z_PK IN
(SELECT t0.Z_PK FROM ZCOUNTRY t0 WHERE t0.ZVISITED = ? )
The includeSubclasses
argument defaults to true
and I’m not sure when you might want to change that?
Delete Everything
If you want to delete all data from the store, regardless of type, use the method deleteAllData()
on the container:
container.deleteAllData()
This method also doesn’t throw or return a value.