SwiftData Deleting Data

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 is true for the deleted model object, and the deleted object is present in the context’s deletedModelsArray.
  • After you save the context, the isDeleted property on the deleted model object is reset to false, the deletedModelsArray is empty.
  • After you save the context, the deleted model is no longer attached to the context and its modelContext property is nil.

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.