Bringing Core Data’s Validation Safety into Modern SwiftData
If you spent any time building app persistence around Core Data over the last decade, you probably remember the model editor. Beyond visual entity mapping, one of its best features was property-level validation. You could click on a String attribute, type 1 into the Minimum Length box, or drop in a Regex pattern, and Core Data would automatically enforce those rules before saving to SQLite.
When SwiftData arrived, it brought a much-needed modern macro-driven approach to data persistence. But while @Model and @Attribute replaced .xcdatamodeld files, property-level validation didn’t quite make the full transition.
I just posted a quick thought about this over on Mastodon:
@MalcolmHall: Just submitted a #SwiftData suggestion, e.g. so we can require save validation like when we want no whitespace and not empty:
@Attribute(minLength: 1, regex: /.*\S.*/) var title: StringI.e. basically the code version of what we used to be able to do in the Core Data model editor.
FB24052006: Enhancement Request: Extend @Attribute macro to support property-level validation constraints (non-whitespace, string length, ranges)
The Precedent Already Exists: @Relationship
What makes this addition feel like a natural next step for SwiftData is that the precedent for save-time validation already exists inside the framework.
If you declare a relationship today, SwiftData lets you supply validation rules directly inside the macro:
@Model
class Kennel {
// Throws an error during modelContext.save() if bounds are broken!
@Relationship(minimumModelCount: 1, maximumModelCount: 5)
var dogs: [Dog]
}
When you mutate dogs, you can interact with it like a standard Swift array. But the moment you invoke try modelContext.save(), the underlying runtime evaluates those bounds. If a constraint is violated, save() fails and throws an error.
Extending that exact lifecycle logic to @Attribute would make model-level validation consistent across the entire schema.
How it Could Work in Code
Because @Attribute is powered by Swift Macros, Swift’s function overloading allows parameter signatures tailored specifically to target types without bloating generic declarations or sacrificing compile-time safety.
For instance, string-specific parameters like minLength, maxLength, or regex could be scoped exclusively to String properties:
Swift
@Model
class Recipe {
// Non-empty, non-whitespace string validation
@Attribute(minLength: 1, regex: /.*\S.*/)
var title: String
// Numeric boundary validation
@Attribute(minimumValue: 1, maximumValue: 720)
var cookTimeMinutes: Int
}
Why Deferred “On-Save” Validation is crucial for SwiftUI
You might wonder why validation shouldn’t just throw inside property setters directly (e.g., try recipe.setTitle("")).
In a SwiftUI-first environment, throwing setters completely break standard two-way data bindings like TextField("Title", text: $recipe.title). By deferring evaluation to modelContext.save(), SwiftUI forms remain fast, fluid, and unhindered during active typing, while domain integrity is guaranteed before writing to disk.
Tracking the Feedback
I’ve logged this formal Enhancement Request with Apple under FB24052006.
If you miss having declarative, schema-level validation guarantees in SwiftData without writing custom boilerplate setters across your ViewModels, feel free to dupe the report or track its status on GitHub: