I'm trying to combine multiple Predicates of the Type with and / or. Previously with CoreData and NSPredicate I'd just do this:
let predicate = NSPredicate(value: true)
let predicate2 = NSPredicate(value: false)
let combinedPred = NSCompoundPredicate(type: .or, subpredicates: [predicate, predicate2])
Is there a comparable way to do this using SwiftData and #Predicate? And if not, how could I implement a way to create partial conditions beforehand and combine them in a predicate later?
The only way I've found of doing this as an expression is like this, but this would make my predicate hundredths of lines long
let includeOnlyFavorites = true
#Predicate { includeOnlyFavorites ? $0.isFavorite : true }
I'm developing an App that allows users to save and query items using shortcut actions. The items are stored using SwiftData and queried using EntityPropertyQuery
Apple implements the Query properties like this:
static var properties = QueryProperties {
Property(\BookEntity.$title) {
EqualToComparator { NSPredicate(format: "title = %@", $0) }
ContainsComparator { NSPredicate(format: "title CONTAINS %@", $0) }
}
}
and later combines the predicates with NSCompoundPredicate.
Closure with Bool return:
let isFavorite = { (item: Item) in item.isFavorite }
let predicate = #Predicate<Item> { isFavorite($0) }
evaluate(Item) -> Bool method but I also can't use thatI also thought i might be able to use StandardPredicateExpression in another predicate because in the documentation it reads:
"A component expression that makes up part of a predicate, and that's supported by the standard predicate type." but there are no further explanations on this type
Wow. This was hard, but i've found a solution to my problem:
If we use PredicateExpression instead of Predicate we can later build a predicate like this:
let expression = PredicateExpressions.Value(true)
let predicate = Predicate<String>({ input in
expression
})
The next step was injecting the input. I chose to just create a closure that takes a variable and returns an expression (because the initialiser that takes expressions does not provide a value, but a variable)
let variableExp = { (variable: PredicateExpressions.Variable<String>) in
let value = PredicateExpressions.Value("Hello There")
return PredicateExpressions.Equal(
lhs: variable,
rhs: value
)
}
let variablePredicate = Predicate<String>({ input in
variableExp(input)
})
Swift Data Model:
@Model
class Book {
@Attribute
var title: String
@Attribute
var lastReadDate: Date
init(title: String, lastReadDate: Date) {
self.title = title
self.lastReadDate = lastReadDate
}
}
Create Closures for the expressions we want to use.
typealias BookVariable = PredicateExpressions.Variable<Book>
typealias BookKeyPath<T> = PredicateExpressions.KeyPath<BookVariable,T>
let dateEquals = { (input: BookKeyPath<Date>, _ value: Date) in
return PredicateExpressions.Equal(
lhs: input,
rhs: PredicateExpressions.Value(value)
)
}
The actual filtering:
// Do we want to filter by date at all?
let filterByDate = true
// The date we want to test against
let testDate = Date.now
let variablePredicate = Predicate<Book>({ input in
// Wrap values
let shouldFilterByDate = PredicateExpressions.Value(filterByDate)
let alwaysTrue = PredicateExpressions.Value(true)
// Create date Expression with testDate
let dateExp = dateEquals(BookKeyPath(root: input, keyPath: \Book.lastReadDate), testDate)
// Predicate that ,
// if shouldFilterByDate evaluates to true returns dateExp result
// otherwise returns expression that evaluates to true
return PredicateExpressions.build_Conditional(
shouldFilterByDate,
dateExp,
alwaysTrue
)
})
let descriptor = FetchDescriptor(predicate: variablePredicate)
modelContext.fetch(descriptor)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With