I have an NSArray of objects, and those objects have 10 properties. I would like to do a text search of those objects.
I know how to search 1 property at a time, but is there an easy way to search ALL properties at once?
Here is a list of properties that my objects have:
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSString * phone;
#property (nonatomic, retain) NSString * secondaryPhone;
#property (nonatomic, retain) NSString * address;
#property (nonatomic, retain) NSString * email;
#property (nonatomic, retain) NSString * url;
#property (nonatomic, retain) NSString * category;
#property (nonatomic, retain) NSString * specialty;
#property (nonatomic, retain) NSString * notes;
#property (nonatomic, retain) NSString * guid;
If I search for "doctor", I would like to see all results where 1 or more of these properties has the word "doctor" in it. For example, if 1 object has a category of "doctor", and another object has an email address of "smith#doctorsamerica.com", they should both show up in the results.
NSString *searchTerm = #"search this";
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF LIKE[cd] %#", searchTerm];
NSArray *filtered = [array filteredArrayUsingPredicate:predicate];
If there is a specific property you can change the predicate to:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.propertyName LIKE[cd] %#", searchTerm];
To search all properties, you will have to bind them together with a logical operator
NSString *query = #"blah";
NSPredicate *predicateName = [NSPredicate predicateWithFormat:#"name contains[cd] %#", query];
NSPredicate *predicatePhone = [NSPredicate predicateWithFormat:#"phone contains[cd] %#", query];
NSPredicate *predicateSecondaryPhone = [NSPredicate predicateWithFormat:#"secondaryPhone contains[cd] %#", query];
NSArray *subPredicates = [NSArray arrayWithObjects:predicateName, predicatePhone, predicateSecondaryPhone, nil];
NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
Add a matches: method to your class:
- (BOOL)matches:(NSString *)term {
if ([self.name rangeOfString:term options:NSCaseInsensitiveSearch| NSDiacriticInsensitiveSearch].location != NSNotFound) {
return YES;
} else if ([self.phone rangeOfString:term options:NSCaseInsensitiveSearch| NSDiacriticInsensitiveSearch].location != NSNotFound) {
return YES;
} else if (...) { // repeat for all of the properties
return YES;
}
return NO;
}
Now you can iterate over your objects checking each one:
NSArray *peopleArray = ... // the array of objects
NSString *someTerm = ... // the search term
NSMutableArray *matches = [NSMutableArray array];
for (id person in peopleArray) {
if ([person matches:someTerm]) {
[matches addObject:person];
}
}
Instead of hardcoding each property name individually, you could get an array of the class's property names: List of class properties in Objective-C, (assuming they were all strings, or you could check if each property's type is of class NSString but I'm not sure how to do this)
and then on each of the objects you'd like to search, you could iterate through each of the object's properties and inspect the value on each:
id objectValue = [object valueForKey:[NSString stringWithUTF8String:propertyName]];
// let's say the property is a string
NSString *objectValueString = (NSString *)objectValue;
Then you could check if the property matches your searchTerm with something like:
BOOL propertyValueMatchesSearchTerm = [objectValueString rangeOfString:mySearchTermString].location != NSNotFound;
if (propertyValueMatchesSearchTerm) {
// add object to search results array
}
Related
I have three realm objects. Department, section and user. section is a kind of sub department in a department. But there will be users under each section and each department.
#interface Department : RLMObject
#property NSString *name;
#property BOOL isCollapsed;
#property (nonatomic, strong) RLMArray<Section> *sections;
#property (nonatomic, strong) RLMArray<User> *users;
#end
#interface Section : RLMObject
#property NSString *name;
#property BOOL isCollapsed;
#property RLMArray<User> *users;
#end
#interface User : RLMObject
#property NSString *department;
#property NSString *email;
#property NSString *firstname;
#property NSString *lastname;
#property NSString *fullname;
#end
What i want to do is, I want to search all the user whose first name and last name contains "a" and linked with department and section.
Now what i am doing is
searchText = [searchText lowercaseString];
RLMResults *sections = [Section objectsWhere:
[NSString stringWithFormat:#"SUBQUERY(users, $user, $user.fullname contains '%#' OR $user.nickname contains '%#').#count > 0",searchText,searchText]];
NSMutableArray *sectionNames = [NSMutableArray array];
for (Section *section in sections)
{
[sectionNames addObject:section.name];
}
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(sections, $section, $section.name IN %#).#count > 0", sectionNames];
RLMResults *filteredDepartments = [[Department objectsWithPredicate:predicate] sortedResultsUsingProperty:#"name" ascending:YES];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"SUBQUERY(users, $user, $user.fullname contains %# OR $user.nickname contains %#).#count > 0",searchText,searchText];
RLMResults *departments = [filteredDepartments objectsWithPredicate:pred];
Right now the issue is there is two sections in sections array but in the deparments results, it returns null. please help. thanks.
Note: If one user already belongs to section then that user will not be include in department.
Maybe you do not need to use subquery. More simply, you can use ANY in query like the following:
[Department objectsWhere:
#"ANY users.firstname CONTAINS %# OR ANY users.lastname CONTAINS %# OR ANY sections.users.firstname CONTAINS %# OR ANY sections.users.lastname CONTAINS %#", searchText, searchText, searchText, searchText];
But I think using inverse relationships is more easier. In Realm, inverse relationships is defined with RLMLinkingObjects.
You can add inverse relationships to User class as follows:
#interface User : RLMObject
...
#property (readonly) RLMLinkingObjects *departments;
#property (readonly) RLMLinkingObjects *sections;
#end
#implementation User
+ (NSDictionary *)linkingObjectsProperties {
return #{#"departments": [RLMPropertyDescriptor descriptorWithClass:Department.class propertyName:#"users"],
#"sections": [RLMPropertyDescriptor descriptorWithClass:Section.class propertyName:#"users"]};
}
#end
Then you can get departments and sections where the user belongs to from User's property, like the following:
RLMResults *users = [User objectsWhere:#"firstname CONTAINS %# OR lastname CONTAINS %#" , searchText, searchText];
for (User *user in users) {
NSLog(#"%#", user.departments);
NSLog(#"%#", user.sections);
}
I have a custom object which contains data as:
#interface Students : NSObject
{
}
#property (nonatomic,strong) NSString *ID;
#property (nonatomic,strong) NSString *FirstName;
#property (nonatomic,strong) NSString *MiddleName;
#property (nonatomic,strong) NSString *LastN
I want to filter it according to the last name. For example:
I want an array which contains all the details of students having last name="Cena"
I tried this:
NSMutableArray *arrayToFilter = self.studentList;
NSString *nameformatString = #"LastName contains[c] a";
NSPredicate *namePredicate = [NSPredicate predicateWithFormat:nameformatString];
[arrayToFilter filterUsingPredicate:namePredicate];
When I run my app I am getting this error and my app crashes:
Terminating app due to uncaught exception 'NSUnknownKeyException', reason:
'[< Students 0x7fd25e102d20> valueForUndefinedKey:]:
this class is not key value coding-compliant for the key a.'
I am only able to get last name in array but I know this is wrong. How can I sort the custom object based on last name.
Code I am using to get lastName:
names = [[NSMutableArray alloc]init];
for (int i = 0; i < [self.studentList count] ; i++)
{
Students * studentsObj = (Students*)[self.studentList objectAtIndex:i];
[names addObject:studentsObj.LastName];
}
Update the predicate to ,
NSString *textToMatch = #"a";
NSPredicate *namePredicate = [NSPredicate predicateWithFormat:#"LastN CONTAINS[cd] %#", textToMatch];
then
NSMutableArray *arrayToFilter = self.studentList;
NSArray *filteredArray = [arrayToFilter filteredArrayUsingPredicate:namePredicate];
for getting last name only,
NSArray *lastNames = [filteredArray valueForKey:#"LastN"];
NSArray *sortedArray = [lastNames sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
I'm trying to understand NSPredicate and it's syntax, when we are using ALL/ANY etc
My class Person:
#interface Person : NSObject
#property (nonatomic, strong) NSString* name;
#property (nonatomic, strong) NSString* surname;
#property NSUInteger age;
#property NSUInteger weight;
#property NSUInteger height;
And I use it for predicates like this:
NSArray* persons = [NSArray arrayWithObjects:personOne, personTwo, personThree, personFour, nil];
My predicate:
NSPredicate* predicateEight = [NSPredicate predicateWithFormat:#"ANY SELF.age > %#", #55];
filteredArray = [persons filteredArrayUsingPredicate:predicateEight];
NSLog(#"Age > 55 %#", filteredArray);
And I've received a mistake:
The left hand side for an ALL or ANY operator must be either an
NSArray or an NSSet
How I need to change my code?
Thanks in advance for any help.
It should like this :
NSPredicate* predicateEight = [NSPredicate predicateWithFormat:#"self.age > %d", 55];
;)
ALL/ANY used for NSArray or NSSet. In your case ANY used for instance of Person.
Example of use ANY:
NSPredicate* predicateEight = [NSPredicate predicateWithFormat:#"ANY SELF.age > %#", #55];
BOOL result = [predicateEight evaluateWithObject:persons];
If you have Array of persons (Array of Arrays):
NSArray* group1 = [NSArray arrayWithObjects:personOne, personTwo, personThree, nil];
NSArray* group2 = [NSArray arrayWithObjects:personFour, personFive, personSix, nil];
NSArray* group3 = [NSArray arrayWithObjects:personSeven, personEight, personNine, nil];
NSArray* groups = [NSArray group1, group2, group3, nil];
NSPredicate* predicateEight = [NSPredicate predicateWithFormat:#"ANY SELF.age > %#", #55];
NSArray* filteredArray = [groups filteredArrayUsingPredicate:predicateEight];
so I am using Magical Record in my project, and I am attempting to do the following:
- (void)persistNewReadingWithOneA:(NSString *)oneA oneB:(NSString *)oneB{
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];
FinalRead *reading1A = [FinalRead MR_createInContext:localContext];
reading1A.a1 = oneA;
reading1A.b1 = oneB;
[localContext MR_saveOnlySelfAndWait];
}
As you can see I've persisted with a new entry and now I need to be able to update that same entry by adding to it's attributes. I tried doing the following:
- (void)updateReadingWithTwoA:(NSString *)twoA twoB:(NSString *)twoB{
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];
FinalRead *reading2A = [FinalRead MR_createInContext:localContext];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"firstname ==[c] %# AND lastname ==[c] %#"];
FinalRead *finalRead = [FinalRead MR_findFirstWithPredicate:predicate inContext:localContext];
reading2A.a2 = twoA;
reading2A.b2 = twoB;
[localContext MR_saveOnlySelfAndWait];
}
Any Ideas?
EDIT:
I am fetching data from a JSON response like so:
- (void)fetchedData:(NSData *)responseData {
if (self.buttonPressed){
//parse out the json data
NSError* error;
NSArray* json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSString *string = [NSString stringWithFormat:#"%#", [json objectAtIndex:0]];
[self persistNewReadingWithOneA:string
oneB:nil];
}else{
//parse out the json data
NSError* error;
NSArray* json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSString *string = [NSString stringWithFormat:#"%#", [json objectAtIndex:0]];
[self persistNewReadingWithOneA:nil
oneB:string];
}
}
and then storing to a newly created object like so:
- (void)persistNewReadingWithOneA:(NSString *)oneA oneB:(NSString *)oneB{
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];
FinalRead *reading1A = [FinalRead MR_createInContext:localContext];
reading1A.a1 = oneA;
reading1A.b1 = oneB;
[localContext MR_saveOnlySelfAndWait];
}
The ViewController then pushes to another that asks another question where once again more JSON is parsed and stored, but into the SAME object that was just created in the previous ViewController. I have the following attributes PER object in NSManagedObject:
#interface FinalRead : NSManagedObject
#property (nonatomic, retain) NSString *a1;
#property (nonatomic, retain) NSString *b1;
#property (nonatomic, retain) NSString *a2;
#property (nonatomic, retain) NSString *b2;
#property (nonatomic, retain) NSString *a3;
#property (nonatomic, retain) NSString *b3;
#property (nonatomic, retain) NSString *a4;
#property (nonatomic, retain) NSString *b4;
#end
So basically, every time that I change a new viewController I'm wanting to add to that same object until the final View has been used.
Your update method also creates a new object:
FinalRead *reading2A = [FinalRead MR_createInContext:localContext];
// ... other stuff
reading2A.a2 = twoA;
reading2A.b2 = twoB;
And the object fetched here:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"firstname ==[c] %# AND lastname ==[c] %#"];
FinalRead *finalRead = [FinalRead MR_findFirstWithPredicate:predicate inContext:localContext];
is completely unused. In fact I would expect that fetch request to crash, because the
predicate uses %# formats for which no arguments are supplied.
To update an object, you have to fetch it first. And to fetch it you need some
attributes which identify the object that you want to update. For example:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"firstname ==[c] %# AND lastname ==[c] %#",
firstName, lastName];
FinalRead *finalRead = [FinalRead MR_findFirstWithPredicate:predicate inContext:localContext];
if (finalRead) {
finalRead.a2 = twoA;
finalRead.b2 = twoB;
// ... save context ...
} else {
// no matching object found
}
Update: The solution in your case might be to pass the finalRead object from one
view controller to the next, instead of re-fetching it in each view controller.
I have a custom NSObject which I need to filter. I have been trying to user NSPredicate to do this, but was unable to so far. Here is my object's structure:
#interface MyBigObject : NSObject
#property (nonatomic, strong) NSString *firstAttribute;
#property (nonatomic, strong) NSString *secondAttribute;
#property (nonatomic, strong) NSMutableArray *featuresArray;
#end
The featuresArray contains other custom objects:
typedef enum {
FeatureExists = YES, //Default
FeatureDoesNotExist = NO,
FeatureNotAvailable
} FeatureValue;
#interface MySmallObject : NSObject
#property (nonatomic, strong) NSString *title;
#property (nonatomic) FeatureValue feature;
#end
I want to only return the objects containing a MySmallObject with a certain title and which has feature == FeatureExists.
I've tried something like (and other variations) but to no success:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(featuresArray, $object, $object.title CONTAINS[c] %# AND $object.feature = %d).#count > 0)", #"Fenced", FeatureExists];
NSLog(#"predicate = %#", predicate);
If I understood correctly, the format of your predicate is wrong. It should be [NSPredicate predicateWithFormat:#"SUBQUERY(featuresArray, $object, $object.title CONTAINS[c] %# AND $object.feature = %d).#count > 0", #"some string", FeatureExists]; where you check in your array if any object matches the requirements.