I am taking some text from a UITextView and using NSPredicate to take that text and see if it matches the data in my data model and database. currently I have to type in exactly word by word to see if it matches. I am using it like this:
- (IBAction)viewResults:(id)sender {
NSEntityDescription *entitydesc = [NSEntityDescription entityForName:#"Chapters" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entitydesc];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"theverse CONTAINS[cd] %#",self.textViewVerse.text];
[request setPredicate:predicate];
NSError *error;
NSArray *matchingData = [context executeFetchRequest:request error:&error];
if(matchingData.count <=0)
{
self.theResults.text = #"NO Verse found";
}
else {
NSString *theVerse = nil;
NSString *chapterNumber = nil;
NSString *chapterName = nil;
NSString *verseNumber = nil;
for (NSManagedObject *obj in matchingData){
theVerse = [obj valueForKey:#"theverse"];
chapterNumber = [obj valueForKey:#"chapternumber"];
chapterName = [obj valueForKey:#"chaptername"];
verseNumber = [obj valueForKey:#"versenumber"];
}
self.theResults.text = [NSString stringWithFormat: #"Chapter Name: %# \n\nChapter Number: %# \n\nVerse Number: %# \n\nVerse: %#",chapterName, chapterNumber, verseNumber, theVerse];
}
}
So if I type in "His wealth and his children will not benefit him" and hit the viewResults button, it will find the verse in the database and display it with its relevant details. However, if I type in "His wealth and his children will not benefit himself" it will display "NO Verse found". Is there a way I can view the verse without having to type in word to word or a word which does not match and still find what I want to find?
You might want to look at the discussion (particularly the comments) that I had on a somewhat similar question on detecting combinations of strings
In your case, you could map all similar words to a unique key, then search for combinations of keys.
Related
I am trying to construct a query of a Core Data store which retrieves an entity's attribute values when they occur in longer string;
i.e, instead of seeking instances where attribute value contains a (shorter) string :
request.predicate = [NSPredicate predicateWithFormat:#"carBrand contains[c] 'merced'"]
I want to find instances (of the entity) whose attribute values are found 'contained in' an arbitrary (longer) string :
NSString* textString = #"Elaine used to drive Audis, but now owns a Mercedes";
request.predicate = [NSPredicate predicateWithFormat:#"%# contains[c] carBrand", textString ];
(ie. retrieve array holding objects with carBrand = #"Audi" and carBrand = #"Mercedes")
In my attempts, NSPredicate doesn't seem to like expressions with the attribute name on the right hand side and throws an error...
[__NSCFConstantString countByEnumeratingWithState:objects:count:]:
unrecognized selector sent to instance 0x
...is there a way of constructing such a query with the attribute name on the left hand side - a 'contained_by' query, as it were?
PS. Searching SO, I've only found solutions by splitting the text into component words which, in my scenario, would be less than ideal! Is this the only type of approach that's viable?
Build a regex string with your array and use MATCHES in your predicate.
[NSPredicate predicateWithFormat:#"%# MATCHES '*(Audi|Mercedes)*'", testString];
To filter cars based on their brand:
NSArray *brands = [#"Audi", #"Mercedes"];
[NSPrediate predicateWithFormat:#"carBrand IN %#", brands];
Decided to try implementing a componentsSeparatedByString approach to build a NSCompoundPredicate
//find alphabetic words omitting standard plurals
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"(?:[^a-z]*)([a-z]+?)(?:s)?\\W+" options:NSRegularExpressionCaseInsensitive error:nil];
//separate with pipe| and split into array
NSString *split = [regex stringByReplacingMatchesInString:textString options:0 range:NSMakeRange(0, speciesString.length) withTemplate:#"$1|"];
NSArray *words = [split componentsSeparatedByString:#"|"];
//build predicate List
NSMutableArray *predicateList = [NSMutableArray array];
for (NSString *word in words) {
if ([word length] > 2) {
NSPredicate *pred = [NSPredicate predicateWithFormat:#"brandName beginswith[c] %#", word];
[predicateList addObject:pred];
}
}
//CarBrand* object;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"CarBrand" inManagedObjectContext:self.managedObjectContext];
request.predicate = [NSCompoundPredicate orPredicateWithSubpredicates:predicateList];
NSError *error =nil;
NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
This retrieves the instances found in the text;
eg1:
#"Elaine used to drive Audis, but now owns a Mercedes";
gives array of objects whose .brandname = "Audi", "Mercedes", respectively .
eg2: #"Among the cars stolen were a Ford Mondeo, a Fiat 500C and an
Alfa-Romeo Spyder"
yields .brandname = "Ford", "Fiat",and "Alfa Romeo"(NB no '-') respectively.
I'm not yet accepting my own answer as it seems too much of a workaround and won't easily extend to (for example) extracting brand-name AND, say, model.
Hopefully, someone will have a better solution!
Contact entity has a many to one relationship to user, which has a userID field
Contact has a status string attribute that can be either 'approved', 'rejected', 'pending' etc
favorite boolean attributes
+ (NSSet *)fetchContactsWithUserID:(NSString *)userID approvedOnly:(BOOL)approvedOnly favoriateOnly:(BOOL)favoriateOnly {
NSString *predString = [NSString stringWithFormat:#"(user.userID = %#)", userID];
if (approvedOnly) {
predString = [predString stringByAppendingString:#" AND (status = approved)"];
}
if (favoriateOnly) {
predString = [predString stringByAppendingString:#" AND (favorite = YES)"];
}
NSPredicate *pred = [NSPredicate predicateWithFormat:predString];
return [self private_fetchEntitiesWithName:#"Contact" predicate:pred];
}
+ (NSSet *)private_fetchEntitiesWithName:(NSString *)name predicate:(NSPredicate *)predicate {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:name inManagedObjectContext:[self private_context]];
[fetchRequest setEntity:entity];
if (predicate) {
[fetchRequest setPredicate:predicate];
}
NSError *error;
NSArray *fetchedObjects = [[self private_context] executeFetchRequest:fetchRequest error:&error]; //CRASH ON THIS LINE!!!!!!
return [NSSet setWithArray:fetchedObjects];
}
stack trace:
There is no error logged out (i already enabled nszombie)
#"(status = approved)" is not a valid predicate. If approved is a string your predicate string should be #" AND (status = \"approved\")" or #" AND (status = 'approved')".
If you use a string literal directly in a predicate you have to enclose it in quotes.
Strings that are not enclosed in quotes are treated as keys by the NSFetchRequest.
This is done because you can actually check if two attributes are equal by using a predicate that compares those two, e.g.#"attribute1 = attribute2".
If you use a predicate format that contains %#, NSPredicate will automatically add quotes if the object you want to use as argument is a NSString.
NSPredicate *p1 = [NSPredicate predicateWithFormat:#"foo = %#", #"Bar"];
NSPredicate *p2 = [NSPredicate predicateWithFormat:#"foo = Bar"];
NSLog(#"%#", p1);
NSLog(#"%#", p2);
yields:
xxx[4673:70b] foo == "Bar"
xxx[4673:70b] foo == Bar
I have the following scenario. I have an app that handles data by using Core Data. I have an entity called "Brothers" which has 3 attributes: Name, Status, Age.
lets say that I have 1 record on my database that has:
-Name ==> Joe (String)
-Status ==> Married (String)
-Age ==> 28 (Integer)
I have an UIlabel a UItextfield and a Button. I want to be able to type the name (in this case Joe) on the UItextfield press the button and display the age (in this case 28) in the UILabel. I would also like to store the age value into a variable type integer so I can make some calculations with it later on.
below the code that I have inside the button.
NSEntityDescription *entitydesc = [NSEntityDescription entityForName:#"Brothers" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entitydesc];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"age %d", [self.age.text integerValue]];
[request setPredicate:predicate];
NSError *error;
NSArray *integer = [context executeFetchRequest:request error:&error];
self.displayLabel.text = integer;
Update #1
I updated the code that i have inside the button and now i am able to search by the name and display the age. I'm still looking into storing the age as an integer into a variable so i can use it later on.
NSEntityDescription *entitydesc = [NSEntityDescription entityForName:#"Brothers" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:entitydesc];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"firstname like %#", self.firstnameTextField.text];
[request setPredicate:predicate];
NSError *error;
NSArray *integer = [context executeFetchRequest:request error:&error];
if(integer.count <= 0){
self.displayLabel.text = #"No records found";
}
else {
NSString *age;
for (NSManagedObject *object in integer) {
age = [object valueForKey:#"age"];
}
self.displayLabel.text = [NSString stringWithFormat:#"age: %#",age];
}
}
A predicate is an expression. If the expression evaluates to true then the predicate is satisfied. So if you were searching by age you'd use e.g.
[NSPredicate predicateWithFormat:#"age = %d", [self.age.text integerValue]]
Or by name:
[NSPredicate predicateWithFormat:#"name = %#", someNameOrOther]
Or by both:
[NSPredicate predicateWithFormat:#"(name = %#) and (age = %d)", someNameOrOther, [self.age.text integerValue]]
A fetch request gets the actual NSManagedObjects. So you'd get back an array of Brothers. Therefore you probably want something more like this to output a name:
if([array count])
self.displayLabel.text = [array[0] name];
Or for an age:
...
self.displayLabel.text = [[array[0] age] stringValue];
Or whatever other property you're outputting.
I think your predicate is wrong, the proper format would be this:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"age.integerValue == %d", [self.age.text integerValue]];
I'm using Core Data and have a to-many relationship with the following entities:
Athlete(evals)<-->>Eval(whosEval)
It starts with a table view that lists ALL athletes in the database. Then when you select an Athlete it pulls up their Evals in a table view. The problem is the way I am doing this is through checking their full name. Unfortunately, it is possible for 2 athletes to have the same name. For this reason, I check their parent's name as well, but I think I am doing it incorrectly. Can anyone explain why the following doesn't work and how I should do it correctly? What happens with this code is if 2 Athletes have the same name, they'll share results. Even if their Parent's Name is different.
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
_managedObjectContext = [appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSFetchRequest *athleteRequest = [[NSFetchRequest alloc] init];
[athleteRequest setEntity:[NSEntityDescription entityForName:#"Athlete" inManagedObjectContext:_managedObjectContext]];
NSError *athleteError = nil;
NSPredicate *athletePredicate = [NSPredicate predicateWithFormat:#"full == %#", _athletesFullName];
[athleteRequest setPredicate:athletePredicate];
NSArray *results = [_managedObjectContext executeFetchRequest:athleteRequest error:&athleteError];
if([results count] >1){
NSPredicate *athletePredicate = [NSPredicate predicateWithFormat:#"pfull == %#", _athletesParentsFullName];
[athleteRequest setPredicate:athletePredicate];
}
Athlete *athleteSelected;
if([results count] >0){
Athlete *currentAthlete = [results objectAtIndex:0];
athleteSelected = currentAthlete;
}
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"whosEval == %#", athleteSelected];
[request setPredicate:predicate];
NSEntityDescription *eval = [NSEntityDescription entityForName:#"Eval" inManagedObjectContext:_managedObjectContext];
[request setEntity:eval];
Modifying athleteRequest after the request has been executed does not have
any effect on the result. Why not simply
NSPredicate *athletePredicate = [NSPredicate predicateWithFormat:#"full == %# AND (pfull == nil OR pfull == %#)",
_athletesFullName, _athletesParentsFullName];
?
Apart from that, it would probably be better to identify the objects by some
unique identifier (e.g. a unique athlete number) instead of relying on name
and parent's name.
Your scenario is really simple, but for more complex situations, you can use compound predicates.
Read this awesome article from NSHipster for more informations:
http://nshipster.com/nspredicate/
My situation is simple: I have some records in my core data store. One of their attributes is a string called "localId". There's a point where I'd like to find the record with a particular localId value. The obvious way to do this is with an NSFetchRequest and an NSPredicate. However, when I set this up, the request returns zero records.
If, however, I use the fetch request without the predicate, returning all records, and just loop over them looking for the target localId value, I do find the record I'm looking for. In other words, the record is there, but the fetch request can't find it.
My other methods in which I use fetch requests and predicates are all working as expected. I don't know why this one is failing.
I want to do this:
- (void)deleteResultWithLocalID:(NSString *)localId {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"WCAAssessmentResult" inManagedObjectContext:context]];
[request setPredicate:[NSPredicate predicateWithFormat:#"localId == %#", localId]];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
NSAssert(error == nil, ([NSString stringWithFormat:#"Error: %#", error]));
if ([results count]) [context deleteObject:[results objectAtIndex:0]];
else NSLog(#"could not find record with localID %#", localId);
[self saveContext];
}
But I end up having to do this:
- (void)deleteResultWithLocalID:(NSString *)localId {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"WCAAssessmentResult" inManagedObjectContext:context]];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
NSAssert(error == nil, ([NSString stringWithFormat:#"Error: %#", error]));
for (WCAAssessmentResult *result in results) {
if ([result.localId isEqualToString:localId]) {
NSLog(#"result found, deleted");
[context deleteObject:result];
}
}
[self saveContext];
}
Any clues as to what could be going wrong?
edit
I've found that I can use the predicate I'm creating to get the results I expect after the fetch request has been executed. So, the following also works:
- (WCAChecklistItem *)checklistItemWithId:(NSNumber *)itemId {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"WCAChecklistItem" inManagedObjectContext:context]];
NSArray *foundRecords = [context executeFetchRequest:request error:nil];
foundRecords = [foundRecords filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"serverId == %#", itemId]];
if ([foundRecords count]) {
return [foundRecords objectAtIndex:0];
} else {
NSLog(#"can't find checklist item with id %#", itemId);
return nil;
}
}
UPDATE
I've come across someone else experiencing this very issue:
http://markmail.org/message/7zbcxlaqcgtttqo4
He hasn't found a solution either.
Blimey! I'm stumped.
Thanks!
If localId is a numerical value, then you should use an NSNumber object in the predicate formation instead of an NSString.
[request setPredicate:[NSPredicate predicateWithFormat:#"localId == %#",
[NSNumber numberWithString:localId]]];
NSPredicate format strings automatically quote NSString objects.
Hate to say it but the most common cause of these types of problems is simple typos. Make sure that your attribute names and the predicate are the same. Make sure that your property names and the attributes names are the same. If a reference to a property works but a reference to attribute name doesn't there is probably a mismatch.
You could test for the latter by comparing the return of:
[result valueForKey:#"localID"]
... with the return of:
result.localID