I have a SwiftUI app with SwiftUI life cycle and am persisting data in Core Data.
I am using Xcode 14.0.1 and iOS 16 to create a NavigationSplitView architecture.
This all works fine. I have added .searchable to the main list and am able to search
the entity string fields in the entity but I want to include string fields in
the relationship entities and have not been able to do so.
Let's say I have an entity Trip, with name, description and comment attributes - all
Strings. I create a searchResults var and use the result in the list. This works for
the fields discussed.
var searchResults: [Trip] {
if searchText.isEmpty {
return Array(tripsFetched)
} else {
return Array(tripsFetched).filter {
$0.wrappedTripName.lowercased().contains(searchText.lowercased())
||
$0.wrappedTripDescription.lowercased().contains(searchText.lowercased())
||
$0.wrappedComment.lowercased().contains(searchText.lowercased())
}//filter
}//if else
}//var search results
Now let's say I have a one to many relationship between Trip and an entity Site and say
Site has string attributes for siteName and siteDescription. I have not been able to
add an iteration over the NSSet of Site objects to look for the searchText. I've made
many attempts including the following but nothing has worked.
||
$0.sites?.allObjects(where: $0.wrappedSiteName.contains(searchText.lowercased()))
Any guidance would be appreciated.
For searching we use the nsPredicate property on the fetch request.
You'll need an or predicate using the contains keyword for the text and equals with either the object or it's id for the relation, but this page shows all the different ways it can be configured:
https://developer.apple.com/documentation/foundation/nspredicate
There is a SwiftUI example on this page:
https://developer.apple.com/documentation/swiftui/fetchrequest/configuration
.onChange(of: query) { value in
quakes.nsPredicate = query.isEmpty
? nil
: NSPredicate(format: "place CONTAINS %#", value)
}
For others - malhal is correct. You can create a compound predicate to search the
one-to-many relationships. The important part is the syntax referenced in his links
that uses the ANY keyword. This is an example:
func cdSearchQuery(searchText: String) {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = Trip.fetchRequest()
let sortDescriptor = NSSortDescriptor(key: "tripName", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
let predicateTrip = NSPredicate(format: "tripName CONTAINS[cd] %# || tripDescription CONTAINS[cd] %# || tripComment CONTAINS[cd] %# || tripCompanions CONTAINS[cd] %#", searchText, searchText, searchText, searchText)
//for the to-many attributes, use the ANY keyword
let predicateSite = NSPredicate(format: "ANY sites.siteName CONTAINS[cd] %# || sites.siteDescription CONTAINS[cd] %# || sites.siteLocation CONTAINS[cd] %#", searchText, searchText, searchText )
let predicate = NSCompoundPredicate(orPredicateWithSubpredicates: [predicateTrip, predicateSite])
fetchRequest.predicate = predicate
do {
let results = try context.fetch(fetchRequest)
//for testing - remove and send results to a Published array
if let tripArray = Array(results) as? [Trip] {
for t in tripArray {
print("included is \(String(describing: t.tripName))")
}
}
} catch {
print("Error retrieving compound predicate")
}
}//cd search
Related
I'm not able to figure out how to sort results from CoreData based on the number of matching relationships.
Concrete situation:
I have Recipe(s) that have one or more RecipeIngredient(s). I have a search interface where I can select multiple Ingredient(s) and want to get back Recipe(s) ordered by how many RecipeIngredient(s) match my selection. Basically I want to find the recipes with the highest number of matching ingredients.
CoreData schema:
Here is what I've done until now, (sort descriptor is just a default until I figure out how to handle this):
func searchRecipes(
searchRequest: RecipeSearchRequest
) -> [CDRecipe] {
let fetchRequest: NSFetchRequest<CDRecipe> = CDRecipe.fetchRequest()
var predicates: [NSPredicate] = []
if searchRequest.searchString != "" {
predicates.append(NSPredicate(format: "title CONTAINS %#", searchRequest.searchString.lowercased()))
}
if searchRequest.savedOnly {
predicates.append(NSPredicate(format: "saved == %#", "true"))
}
// HERE
if !searchRequest.includedIngredientIDs.isEmpty {
predicates.append(NSPredicate(format: "ANY ingredients.ingredient.identifier IN %#", searchRequest.includedIngredientIDs))
}
fetchRequest.predicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
let sortDescriptor = NSSortDescriptor(keyPath: \CDRecipe.title, ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchRequest.fetchLimit = searchRequest.limit
do {
let recipes = try coreDataStack.managedObjectContext.fetch(fetchRequest)
return recipes
} catch {
print(error.localizedDescription)
return []
}
}
I have also considered pulling all the recipes in my database and doing this sorting in memory. However I have 10K+ recipes and will have more in the future so doing this will would be highly inefficient.
I have a 'tags' array stored in CoreData (type 'Transformable') as an Array.
I am making a request to CoreData to retrieve tags based on a searchText (string) input.
How can I use NSPredicate to filter the tags array based on the searchText?
let request = NSFetchRequest<NSFetchRequestResult>(entityName: Constants.Trip)
if(searchText != nil && searchText != "") {
request.predicate = NSPredicate(format: "ANY %# in tags", searchText!)
}
request.returnsObjectsAsFaults = false
You cannot do that. Check this answer core data array of string filter
You might consider to have an entity for tags, or join the array of tags and have it as a whole string in your current entity, thus having the tags property as type String. For the last approach, you could search with the predicate below.
NSPredicate(format: "tags CONTAINS[cd] %#", searchText)
The [cd] expression is to specify case and diacritic insensitivity respectively.
You can get know more about the Core Data operations here.
I got some basic operators from the documentation and put them below:
BEGINSWITH
The left-hand expression begins with the right-hand expression.
CONTAINS
The left-hand expression contains the right-hand expression.
ENDSWITH
The left-hand expression ends with the right-hand expression.
LIKE
The left hand expression equals the right-hand expression: ? and * are allowed as wildcard characters, where ? matches 1 character and * matches 0 or more characters.
You need to write predicate and set as a part of request like this ...
func getEntitiesWithName(_ entityName:String, sortKey:String?, predicate: NSPredicate?, ascending:Bool, context:NSManagedObjectContext) -> [NSManagedObject] {
var results:[NSManagedObject] = [NSManagedObject]()
context.performAndWait { () -> Void in
let request = NSFetchRequest<NSFetchRequestResult>()
request.entity = NSEntityDescription.entity(forEntityName: entityName, in: context)
if let fetchPredicate = predicate {
request.predicate = fetchPredicate
}
if let key = sortKey {
let sortDescriptor = NSSortDescriptor(key: key, ascending: ascending, selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))
request.sortDescriptors = [sortDescriptor];
}
results = (try! context.fetch(request)) as! [NSManagedObject]
}
return results
}
And you can set predicate like this
let predicate = NSPredicate(format: "isActive == %#", NSNumber(value: true as Bool))
I have two simple Entities:
Part {
#NSManaged var states: Set<PartState>
}
PartState {
#NSManaged var date: Date
#NSManaged var state: Int
}
I want to fetch those entities from Part, that newest PartState has state == 1.
I've tried to create NSPredicate:
NSPredicate(format: "SUBQUERY(states, $s, $s.date == max($s.date) AND $s.state == 1).#count > 0")
It looks like max($.date) checks all existing PartState entities, not just those belonging to a given Part and always returns 1 or 0 results.
NSPredicate(format: "ANY (states.state == 1 AND states.date == states.#max.date)")
Fails with: Unable to parse the format string...
NSPredicate(format: "ANY states.state == 1 AND ANY states.date == states.#max.date")
Returns all entities that have ever had state == 1 (which is what I would expect).
The only other solution that comes to my mind, is to have newestState property in Part but I still looking for some NSPredicate-based way.
You can fetch latest entry based on date using sort Descriptor
and apply predicate to fetch which has state == 1
let fetchRequest = NSFetchRequest<Part>.init(entityName: "Part")
fetchRequest.sortDescriptors = [NSSortDescriptor.init(key: "states.date", ascending: false)]
let predicate = NSPredicate.init(format: "states.state = %#","1")
I am a new hand to Core Data,I don't know how to write some query sentence for NSPredicate and meet some problem.Please help to find my problem and give me some suggestion to achieve my query aim.For example,now I Have a table ,it has some data .(As the picture show)My database
The data in the table with the month attribute "2015/03/01 00:00:00".Now,I want to query a date which month is began with 2015/03,I code "fetchRequest.predicate = NSPredicate(format: "month BEGINSWITH[cd] %#/0%#", year,month)" or "fetchRequest.predicate = NSPredicate(format: "month LIKE[c] %#/0%#", year,month)",am I right?(year=2015,month=03)
If not,I really hope someone can help me.And another question is the 'cd/c' in [] means what?Is it set structures?The code as follow:
func remove(model:MonthlySummery) -> Int {
let cxt = self.managedObjectContext!
let entity = NSEntityDescription.entityForName("MonthlySummery", inManagedObjectContext: cxt)
let component = NSCalendar.currentCalendar().components([.Year,.Month], fromDate: model.month!)
let year = component.year
let month = component.month
let fetchRequest = NSFetchRequest()
fetchRequest.entity = entity
NSLog("month == %i/0%i", year,month)
fetchRequest.predicate = NSPredicate(format: "month BEGINSWITH[cd] %#/0%#", year,month)
do{
let listData = try cxt.executeFetchRequest(fetchRequest) as NSArray!
if listData.count > 0 {
let dailyRecord = listData.lastObject as! NSManagedObject
cxt.deleteObject(dailyRecord)
if cxt.hasChanges {
try cxt.save()
}
}
} catch {
NSLog("Remove failure!")
let nserror = error as NSError
NSLog("Error: \(nserror), \(nserror.userInfo)")
}
return 0
}
It is a bad idea to parse dates by relying on formatted date strings.
Refactor your code to use proper dates. The NSManagedObject subclass should have a property of type NSDate. In the actual SQLite database, this will be represented by a long number, but you should not be interested in that.
You then calculate start and end date of particular month and use a predicate like this:
NSPredicate(format: "month >= %# && month < %#", start, end)
When using a NSPredicate, I'm trying to search all objects (strings) contained within an array. The code sample below works but the predicate only collects the object in the first index only?? The NSPredicate is used for a CKQueryOperation.
Each Record has a value for a key named Category.
let array: [String] = ["Education", "Sport", "TV and Film"]
// select all records
//let predicate = NSPredicate(format: "Category = %#", category )
let predicate = NSPredicate (format: "Category == %#", argumentArray: array)
let query = CKQuery(recordType: "quizRecord", predicate: predicate)
// get just one value only
let operation = CKQueryOperation(query: query)
//code works but only queries Records with the Category for "Education"
Try replacing:
"Category == %#"
With:
"Category IN %#"
I was having the same issue as Peter Wiley. The solution I found was a mash up of Danny Bravo's solution and comment on that solution.
let arrayPredicate = NSPredicate(format: "Name IN %#", argumentArray: [names])
To get multiple results I needed to use both the keyword "IN" in the format and wrap my argumentArray with [].