Search in array of different objects - ios

I'm struggling with my predicate search here, probably a lack of vocabulary.
I have a (kind of) weak design here that my deadline doesn't allow me to change too deeply, and the situation is the following.
I'm searching in a tableview of contacts AND users, my two objects of concern here. The search works fine when there are only contacts (and none of the objects listed are users).
Now if it so happens (which it will, very often) that some of these contacts are users, when I search the tableview, my predicate key doesn't match, and I obviously get an exception.
How could I proceed to go around this, knowing I would like the search to still include everyone. (I have a backup plan where I just remove the users from the search feature and it works like a charm).
I tried using the OR in my predicate, like so :
#"compositeName contains[c] %# OR name contains[c] %#" //(where %# is my search string)
but it's not enough to skip the fact that my contact has a "compositeName" and my user has a "name", which causes an exception.
I don't want to modify the key name of my users to "compositeName" because it would imply to reinstall the app for all my beta testers, which is (due to deadlines and app-generated content) also not possible. Unless there is a way for them to have the new data model without having to reinstall, then I would do that and simply call them all "compositeName". (or "name").
I'm using core data and the array is full of NSManagedObject (of user & contact types).
Any idea?
EDIT 1 : Is this a good idea?
I'm thinking of splitting the arrays if there are users, then using different predicates in both, then finally combine them again and show those results. But it seems like a lot of processing and maybe there is something more efficient?
EDIT 2: Crash log
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ valueForUndefinedKey:]: the entity User is not key value coding-compliant for the key "compositeName".'
EDIT 3 : Class check
According to comments and other posts on stack, you can actually add a condition check, and also do class checks in predicate. Now I tried to combine both by writing this :
#"((className == %#) name contains[c] %#) OR ((className == %#) compositeName contains[c] %#)", #"User",searchText, #"Contact", searchText
which isn't a correct format. This is the first time i'm playing with predicates so if you have any clue how to write a predicate that says
(If class = User, then name contains [c], if class = Contact, then compositeName contains [c])

Your issue is that your combined predicate is going to send valueForKey for both of the properties to each object - and a given object only supports 1 of the two properties, so you get the exception.
If you combine an object class test using an AND with the name/compositeName test you can avoid this issue - by testing the class first, the predicate will skip the second test (for name/compositeName) because the first test has already returned false - making it pointless checking the second clause of the AND.
You can use a predicate something like
#"(className == %# AND name contains[c] %#) OR (className == %# AND compositeName contains[c] %#)",[User className],searchString,[Contact classname],searchString

Related

Core Data recursive search using NSPredicate

I have a project using Core Data with "folder" and "document" entities. A folder can contain many documents, and also other folders.
I'm working on a search feature, and in the current version I have something like this which finds documents/folders within a particular folder that have a name that partially matches the search text:
let predicate = NSPredicate(format: "parentFolder == %# AND name CONTAINS[cd] %#", folderToSearch, searchText)
fetchedResultsController.fetchRequest.predicate = predicate
However, I'd like it to also search within folders that are children of the current one (and if it finds a match, include the containing folder in the results).
For example, if my folder/document structure was like this (assume the Shopping folder and Travel folder are contained within the same parent folder):
Shopping
Groceries
- toothpaste
- cheese
Travel
Packing
- toothpaste
- toothbrush
... and I searched for "toothpaste", I'd want the search to return the "Shopping" folder object and the "Travel" folder object.
What I need is something like this:
let predicate = NSPredicate(format: "parentFolder == %# AND name (OR ANY OF IT'S CHILDREN'S NAMES) CONTAINS[cd] %#", folderToSearch, searchText)
fetchedResultsController.fetchRequest.predicate = predicate
Is there any way to form an NSPredicate to do what I want?
Or something else I should consider doing?
UPDATE: Yes, there is a similar question to this already. However, the accepted answer there suggests using a "transient boolean property" and using that in the NSPredicate... but everything else I've read says that won't work when using an SQLite store. (See this and this.) So I think my question is still valid. I've also updated my question to clarify exactly what I need.
Note that when I first posted this question, I incorrectly thought about adding a transient property to determine if a document/folder is a child/grandchild/etc. of the folder that the search started from, and changed the predicate to this:
let predicate = NSPredicate(format: "isDescendentOfSearchParent == YES AND name CONTAINS[cd] %#", searchText)
fetchedResultsController.fetchRequest.predicate = predicate
... but quickly learned that transient properties don't work in NSPredicates when used with an SQLite store. (Plus, this would return the document/folder that matched the search text, not its containing folder... which isn't what I want.)

Is it possible to include a [NSPredicate predicateWithBlock:] in a NSCompoundPredicate? [duplicate]

I am playing with an app that uses Core Data and NSManagedObjects to populate a UITableView. There is only one class in my application, called Event. I have created the following custom instance method on Event:
- (BOOL)isExpired {
return ([[self.endOn dateAtEndOfDay] timeIntervalSinceNow] < 0);
}
I would like to limit the UITableView that displays Event objects to only the Events that are expired - that is, where isExpired returns YES. I have tried to do this by adding an NSPredicate to the NSFetchRequest:
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary * bindings) {return([evaluatedObject isExpired]);}];
[fetchRequest setPredicate:predicate];
but I get the error: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Problem with subpredicate BLOCKPREDICATE(0x272ac)'
***. Does this mean that you can't use a block predicate with an NSFetchRequest? Or have I just constructed it improperly?
Thank you!
So, it appears that we've established in the comments to the original post that this is likely caused by SQLite stores being incompatible with block predicates, since Core Data cannot translate these to SQL to run them in the store (thanks, JoostK).
There might be a couple of ways to overcome this:
Provided that the end date of your entities is a regular attribute, you might be able to express the expiry constraint as a predicate format string instead of a block predicate, which Core Data should be able to translate into a SQL clause.
If the above is possible, you will probably prefer to use a fetch request template to retrieve the expired items. You would need to pass in a substitution variable like $NOW to give access to the current date, though. This has the advantage of making the predicate template show up in the model editor.
Both approaches, however, have the disadvantage of duplicating existing functionality (i.e., your isExpired method). So another way would be fetch all qualifiying entities regardless of their expiry state first, and then run a dedicated filtering step on the resulting set of entities to weed out the non-expired ones. Since by that point, they have been fully resurrected from the store, you should be able to use a block predicate for this.
You can do a normal fetch request without specifying the predicate, and afterwards filter the resulting array:
NSArray *allEvents = [context executeFetchRequest:fetchRequest];
if (!allEvents) { // do error handling here
}
NSArray *expiredEvents = [allEvents filteredArrayUsingPredicate:predicate];

FetchRequest with NSPredicate not finding results?

I am storing data in a table that has the columns "name" and "series". I am using this NSPredicate to query:
var request : NSFetchRequest = NSFetchRequest(entityName: "Entry")
request.returnsObjectsAsFaults = false
request.predicate = NSPredicate(format: "name = %# AND series = %#", name, series)
return request
The 'name' and 'series' variables are passed in as String arguments, which are also the data types on the table.
For some reason, this query returns no data. If I do a fetch request on this table without specifying a predicate I can see that the data is indeed there. I am not sure what I'm doing wrong.
For what it's worth I have tried enclosing the conditionals in parens but that didn't seem to make a difference either. Thanks in advance!
UPDATE: I've tried many different things but so far nothing is working. I'm really stumped.
For what it's worth it seems like I am having the same issue as this person: NSPredicate Returns No Results with Fetch Request, Works with Array Filtering
But, there isn't anything on that entry stating specifically what the solution to the problem was.
You can check if the data is there by
printing the URL of the application documents directory to the console.
going to this directory in Terminal
running sqlite3 <databaseName>
trying select * from z<entityName> where name = '<nameData>'
You will be able to explore the data and check if it contains what you expect. Don't forget to also check the content of your name and series variables in the code.
Oh wow...thank you everyone so much for all your answers. In the end it turned out that when I was populating the id for inserting a new row into the table, I was looking at a different table to calculate the new primary key id.
So I was overwriting existing records, which is why my query kept failing.
Kids, if you copy/paste your code without rigorously checking it, you're gonna have a bad time.

'NSInvalidArgumentException', reason: 'Unsupported predicate (null)'

I am getting this Unsupported predicate error, while trying to instantiate a
NSFetchedResultsController with an aggregate Predicate.
My Coredata structure looks like this - There is a PurchaseDetails Entity which has n number of Payment Entity (One to many relationship). Each Payment has a type, for eg.Cash, Cheque , Other etc. Now I need to get the list of all the PurchaseDetails where Payment.type = Cash.
The NSPredicate looks like this: #"ALL payments.type = 'cash'"
Are we not supposed to use ALL ?
The predicate isn't crashing on creation rather it crash while executing the fetch request.
But the same fetch request works fine when used separately without using a NSFetchedResultsController. They both wont work together ?
Yes you need to use ALL if payments is a to-many relationship. Note that this means that every single payment's type will be #"cash". If you just want at least one cash payment, use ANY.
The (null) in the error message hints at something else though. Did you attach the predicate to the fetch request?
Also, did you try to set cache:nilin your fetched results controller? The FRC could be the reason you get stale results.

keypath objectID not found in entity exception

I'm facing this strange issue when trying to fetch some objects after their objectID. The error complains that there is no such thing as an objectID key path, but the managed object should respond to that. The error is not thrown all the time, which makes me think it could be a concurrency problem, although I've double checked and each context performs the operations on its own thread.
Here is the predicate, although it looks sane to me:
[NSPredicate predicateWithFormat:#"objectID == %#", book.objectID]
Edit: Regarding this answer. I didn't tried using the object itself, I need to use the objectID because of multithreading considerations.
The answer given in THIS link still holds here.
It holds in the sense that you use the same predicate:
NSPredicate* p = [NSPredicate predicateWithFormat:#"SELF = %#",book.objectID];
But you supply the objectID as parameter.
CoreData allow comparison of objectIDs and objects when fetching from the store. it probably has to do with the fact that an objectID is used for a one-to-one mapping to a managed object (hence the use of SELF which is a predicate reserved word and not a property name).

Resources