Predicates
Predicates define filter conditions for queries. SwiftDataServer provides the #Predicate macro for 100% API compatibility with Apple's SwiftData.
The #Predicate Macro
Create type-safe predicates using a closure syntax:
// Using $0 shorthand
let adults = #Predicate<User> { $0.age >= 18 }
// Using named parameter
let adults = #Predicate<User> { user in
user.age >= 18
}
Comparison Operators
// Equality
let byName = #Predicate<User> { $0.name == "Alice" }
// Inequality
let notAdmin = #Predicate<User> { $0.role != "admin" }
// Less than
let young = #Predicate<User> { $0.age < 18 }
// Less than or equal
let youngOrExact = #Predicate<User> { $0.age <= 17 }
// Greater than
let senior = #Predicate<User> { $0.age > 65 }
// Greater than or equal
let adults = #Predicate<User> { $0.age >= 18 }
Logical Operators
// AND
let activeAdults = #Predicate<User> { user in
user.age >= 18 && user.isActive == true
}
// OR
let specialUsers = #Predicate<User> { user in
user.role == "admin" || user.role == "moderator"
}
// NOT
let notActive = #Predicate<User> { !($0.isActive == true) }
// Complex combinations
let complex = #Predicate<User> { user in
(user.age >= 18 && user.isActive) || user.role == "admin"
}
String Operations
// Contains
let searchName = #Predicate<User> { $0.name.contains("John") }
// Has prefix
let doctorsPrefix = #Predicate<User> { $0.name.hasPrefix("Dr.") }
// Has suffix
let gmailUsers = #Predicate<User> { $0.email.hasSuffix("@gmail.com") }
// Case-insensitive contains (uses LOWER() in SQL)
let searchInsensitive = #Predicate<User> {
$0.name.localizedStandardContains("john")
}
Nil Checks
// Is nil
let noEmail = #Predicate<User> { $0.email == nil }
// Is not nil
let hasEmail = #Predicate<User> { $0.email != nil }
Using Variables
Reference external variables in your predicates:
let searchTerm = "Alice"
let minAge = 21
let predicate = #Predicate<User> { user in
user.name.contains(searchTerm) && user.age >= minAge
}
Using Predicates
// With FetchDescriptor
let adults = #Predicate<User> { $0.age >= 18 }
let descriptor = FetchDescriptor<User>(predicate: adults)
let users = try context.fetch(descriptor)
// Using builder pattern
let users = try context.fetch(
FetchDescriptor<User>()
.filter(adults)
.sorted(by: SortDescriptor(propertyName: "name"))
)
Combining Predicates
Combine existing predicates programmatically:
let adults = #Predicate<User> { $0.age >= 18 }
let active = #Predicate<User> { $0.isActive == true }
// Combine with AND
let activeAdults = adults.and(active)
// Combine with OR
let eitherCondition = adults.or(active)
// Negate
let minors = adults.negated
Struct-Based Construction
For dynamic predicate building, construct predicates programmatically:
// Simple comparison
let adults = Predicate<User>(expression: .comparison(
keyPath: "age",
op: .greaterThanOrEqual,
value: .int(18)
))
// Between range
let middleAged = Predicate<User>(expression: .between(
keyPath: "age",
low: .int(30),
high: .int(50)
))
// In list
let specificRoles = Predicate<User>(expression: .in(
keyPath: "role",
values: [.string("admin"), .string("moderator")]
))
Supported Operations Summary
| Operation | SQL |
|---|---|
== | = |
!= | != |
< | < |
<= | <= |
> | > |
>= | >= |
&& | AND |
|| | OR |
! | NOT |
.contains() | LIKE '%...%' |
.hasPrefix() | LIKE '...%' |
.hasSuffix() | LIKE '%...' |
== nil | IS NULL |
!= nil | IS NOT NULL |
Next Steps
- Fetch Descriptors - Configure complete queries
- Sorting - Sort query results