I am working in Xcode 15 (beta) migrating to SwiftData and am having a hard time figuring out how to form a search predicate for my one to many model. The desired result is to return a query that returns only the articles and sections where the article "search" field contains the string a user is searching for.
Here is my current model:
@Model
class SectionsSD {
@Attribute(.unique) var id: String
var section: String
var rank: String
var toArticles: [ArticlesSD]?
init(id:String, section: String, rank: String) {
self.id = id
self.section = section
self.rank = rank
}
}
@Model
class ArticlesSD {
var id: String
var title: String
var summary: String
var search: String
var section: String
var body: String
@Relationship(inverse: \SectionsSD.toArticles) var toSection: SectionsSD?
init(id: String, title: String, summary: String, search: String, section: String, body: String) {
self.id = id
self.title = title
self.summary = summary
self.search = search
self.section = section
self.body = body
}
}
In CoreData I was able to do the following in my code to form and pass the search predicate ("filter" being the user input search text, "SectionsEntity" being my old CoreData model):
_fetchRequest = FetchRequest<SectionsEntity>(sortDescriptors: [SortDescriptor(\.rank)], predicate: NSPredicate(format: "toArticles.search CONTAINS %@", filter))
I can't find any examples or info anywhere that explains how to form a predicate in SwiftData to achieve the same search results. I can't seem to find a way to represent toArticles.search properly assuming the capability is there. Here is what I've tried but Xcode complains about the $0.toArticles?.contains(filter) with errors about "Cannot convert value of type 'Bool?' to closure result type 'Bool'" and "Instance method 'contains' requires the types 'ArticlesSD' and 'String.Element' (aka 'Character') be equivalent"
let searchPredicate = #Predicate<SectionsSD> {
$0.toArticles?.contains(filter)
}
_sectionsSD = Query(filter: searchPredicate)
I've tried $0.toArticles?.search.contains(filter) but Xcode can't seem to find its way there like it did using CoreData
Any suggestions and examples on how to form a predicate in this use case would be appreciated.
Thank you for this.
I decided to check whether the logic of the predicate expression was wrong or the #Predicate
itself was doing something, and it turns out it's the latter.
Instead of filtering the sections within the query, I moved it outside to the regular place filtering happens. This works as expected with the sections being filtered correctly by the filter text.
@Query private var sections: [SectionsSD]
let filter: String
var filteredSections: [SectionsSD] {
sections.filter {
$0.toArticles.flatMap {
$0.contains { $0.search.contains(filter) }
} == true
// or as before
// $0.toArticles?.contains { $0.search.contains(filter) } == true
}
}
List(filteredSections) { ... }
So it's when placed inside the #Predicate
are things not working.
It is definitely worth noting that we are only in the second beta of SwiftData so there is always the chance things don't work because of internal bugs. For example, in the release notes is this which could be the reason your predicate isn't working.
SwiftData
Known Issues
• SwiftData queries don't support some
#Predicate
flatmap
andnil
coalescing behaviors. (109723704)
It's best to wait for the next few betas to see if anything changes, and maybe new features might make things easier.