The SwiftData predicate documentation says that it supports the contains(where:) sequence operation in addition to the contains(_:) string comparison but when I put them together in a predicate to try and perform a tokenised search I get a runtime error.
Unsupported subquery collection expression type (NSInvalidArgumentException)
I need to be able to search for items that contain at least one of the search tokens, this functionality is critical to my app. Any suggestions are appreciated. Also does anyone with experience with CoreData know if this is possible to do in CoreData with NSPredicate?
import SwiftData
@Model
final class Item {
var textString: String = ""
init() {}
}
func search(tokens: Set<String>, context: ModelContext) throws -> [Item] {
let predicate: Predicate<Item> = #Predicate { item in
tokens.contains { token in
item.textString.contains(token)
}
}
let descriptor = FetchDescriptor(predicate: predicate)
return try context.fetch(descriptor)
}
let predicate: Predicate<Item> = #Predicate { item in tokens.contains { token in item.textString.contains(token) } }
This isn't going to work. Other than doing string comparisons for an attribute, contains
only works with a to-many relationship (and is eventually converted to a subquery
on the relationship).
It sounds like you need to merge unknown number of predicates, which is not supported by #Predicate
because it can't handle for loops.
To achieve your goal, consider creating your own predicate using PredicateExpressions
. There are some discussions on that topic and here is an example that may help: <https://forums.developer.apple.com/forums/thread/736166>.
Just in case this matters, starting from iOS 17.4, you can dynamically combine predicates, as shown in the following code example:
let predicate1 = #Predicate<Item> {
$0.textString.contains(token1)
}
let predicate2 = #Predicate<Item> {
$0.textString.contains(token2)
}
let orPredicate = #Predicate<Item> {
predicate1.evaluate($0) || predicate2.evaluate($0)
}
I know this isn't what you are looking for because you have a unknown number of tokens.
In the Core Data world, you can use NSPredicate
NSCompoundPredicate
, like this:
let subPredicates = tokens.map {
NSPredicate(format: "textString CONTAINS %@", $0)
}
let predicate = NSCompoundPredicate(orPredicateWithSubpredicates: subPredicates)
Best,
——
Ziqiao Chen
Worldwide Developer Relations.