The Setup
I was building Inbox/Archive list views for a document scanning app. Simple enough: filter documents by status using SwiftData’s @Query. Two compile-time surprises were waiting.
Pitfall 1: #Predicate Can’t Compare Enums
What I Wrote
The natural approach:
@Model
class Document {
var status: DocumentStatus // enum stored property
// ...
}
enum DocumentStatus: String, Codable {
case inbox, archived
}
Then in the view:
@Query(
filter: #Predicate<Document> { $0.status == .inbox },
sort: \Document.createdAt,
order: .reverse
) private var documents: [Document]
Looks clean. Doesn’t compile.
Why It Fails
#Predicate is a Swift macro that translates predicate expressions into something Core Data can execute under the hood. It only supports primitive types for comparisons — String, Int, Bool, and friends. Enum types aren’t in that set.
SwiftData does store enums as their rawValue internally, but the #Predicate macro doesn’t auto-bridge that conversion at the expression level.
The Fix: rawValue Stored Property Pattern
@Model
class Document {
var statusRawValue: String // #Predicate compatible
var status: DocumentStatus { // convenience computed
get { DocumentStatus(rawValue: statusRawValue) ?? .inbox }
set { statusRawValue = newValue.rawValue }
}
init(title: String, imageData: Data) {
self.statusRawValue = DocumentStatus.inbox.rawValue
// ...
}
}
Now #Predicate gets a plain String comparison:
@Query(
filter: #Predicate<Document> { $0.statusRawValue == "inbox" },
sort: \Document.createdAt,
order: .reverse
) private var documents: [Document]
App code still uses the type-safe document.status = .archived interface. Only storage and queries go through the rawValue.
Pitfall 2: modelContext Is nil on Empty @Query Results
What I Wrote
I needed a ModelContext in InboxView to save scanned documents. My first instinct:
struct InboxView: View {
@Query(...) private var documents: [Document]
func saveDocument() {
if let context = documents.first?.modelContext {
// save document...
}
}
}
When the Inbox is empty — like on first launch — documents is an empty array. documents.first is nil. No modelContext. A first-time user literally can’t save their first scan.
The Fix: @Environment
struct InboxView: View {
@Query(...) private var documents: [Document]
@Environment(\.modelContext) private var modelContext
func saveDocument() {
// always available
let document = Document(title: "New", imageData: data)
modelContext.insert(document)
}
}
@Environment(\.modelContext) is always available as long as a modelContainer is injected into the view hierarchy. It doesn’t depend on query results.
Takeaways
#Predicateonly compares primitive types. To filter by enum, keep a rawValue stored property alongside a computed enum accessor.- Never get modelContext from
@Queryresults. Empty lists mean nil context.@Environment(\.modelContext)is the reliable path.
Neither of these pitfalls is clearly documented. You find out when the compiler tells you — or worse, when a user reports that saving doesn’t work on an empty list.