Using NSPredicate to search through array of objects - objective-c

I have an array of instances of a class called Contact, which has, among others, the following properties:
NSArray *mailAddressList // Array of NSString
NSArray *websiteList // Array of NSString
NSArray *tags // Array of instances of Tag class
The class tag has the following properties:
NSString *name;
UIColor *color;
I want to use NSPredicate to search a string in any property of each Contact. This is the code I have:
if([scope isEqualToString:SCOPE_MAIL] || [scope isEqualToString:SCOPE_WEBSITE])
{
// Search through an array
predicate = [NSPredicate predicateWithFormat:#"ANY SELF.%# contains[c] %#", scope, textSearch];
}
else if([scope isEqualToString:SCOPE_TAG])
{
// Search another object's property
predicate = [NSPredicate predicateWithFormat:#"SELF.%#.name contains[c] %#", scope, textSearch];
}
else
{
// The rest of the properties are instances of NSString
predicate = [NSPredicate predicateWithFormat:#"SELF.%# contains[c] %#", scope, textSearch];
}
Everything works fine except for SCOPE_TAG, it doesn't return any values. I don't think I'm using the predicate correctly.
NOTE: I'm new with NSPredicate so I would like to hear some insights if what I'm doing is not ok

First of all, if you substitute a keypath you should use %K as arg.
Further, I think you are missing the ANY argument in your second query. I think you want a result if any of the tag names contains your textSearch.
To get a better understanding of how predicates work, have a look at the Apple Documentation
I did a quick test and it is still working fine:
NSMutableArray *arrayContacts = [NSMutableArray array];
{
AMContact *contact = [[AMContact alloc] init];
NSMutableArray *arrayTags = [NSMutableArray array];
{
AMTags *tag = [[AMTags alloc] init];
tag.name = #"Test";
[arrayTags addObject:tag];
}
{
AMTags *tag = [[AMTags alloc] init];
tag.name = #"Te2st2";
[arrayTags addObject:tag];
}
{
AMTags *tag = [[AMTags alloc] init];
tag.name = #"No";
[arrayTags addObject:tag];
}
contact.tags = [arrayTags copy];
[arrayContacts addObject:contact];
}
{
AMContact *contact = [[AMContact alloc] init];
NSMutableArray *arrayTags = [NSMutableArray array];
{
AMTags *tag = [[AMTags alloc] init];
tag.name = #"Test";
[arrayTags addObject:tag];
}
contact.tags = [arrayTags copy];
[arrayContacts addObject:contact];
}
NSPredicate *pred = [NSPredicate predicateWithFormat:#"ANY SELF.%K.name contains[c] %#", #"tags", #"Test"];
NSArray *result = [arrayContacts filteredArrayUsingPredicate:pred];
NSLog(#"%#", result);

Related

Filter array of custom objects with a dictionary property

I have an array that holds MyCustomObject objects.
MyCustomObject has 3 properties:
NSString *id;
NSString *name;
NSDictionary *phones;
How do I filter that array by the content of the "phones" property?
All I saw online is:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"phones CONTAINS[c] %#",textField.text];
self.filteredArray = [self.unfilteredArray filteredArrayUsingPredicate:predicate];
But it doesn't help me much...
Thanks
MyCustomObject *value = [[MyCustomObject alloc] init];
for(value in arrayname)
{
NSString *str = [value.phones objectForKey:#"key"];
NSRange r = [str rangeOfString:textField.text options:NSCaseInsensitiveSearch];
if(r.location != NSNotFound)
{
NSLog(#"Match found");
}
}
self.filterArray = [self.unfilteredArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
MyCustomObject *obj = (MyCustomObject*)evaluatedObject;
return ([[[obj.phones objectForKey:#"key"] lowercaseString] rangeOfString:[textField.text lowercaseString]].location != NSNotFound );
}]];

obj c -get list of indexes in NSArray from NSPredicate

I have an array of car and I am filtering it based on objects containing the letter i.
NSMutableArray *cars = [NSMutableArray arrayWithObjects:#"Maruthi",#"Hyundai", #"Ford", #"Benz", #"BMW",#"Toyota",nil];
NSString *stringToSearch = #"i";
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains[c] %#",stringToSearch]; // if you need case sensitive search avoid '[c]' in the predicate
NSArray *results = [cars filteredArrayUsingPredicate:predicate];
results contains Maruthi,Hyundai. Instead of the elements, I want results to contain the indexes of the elements i.e 0,1.
NSMutableArray *cars = [NSMutableArray arrayWithObjects:#"Maruthi",#"BMW", #"Ford", #"Benz", #"Hyundai",#"Toyota",nil];
NSMutableArray * results = [[NSMutableArray alloc]init];
for(int i = 0;i<cars.count;i++)
{
NSString * obj = [cars objectAtIndex:i];
if([obj rangeOfString:#"i"].location == NSNotFound)
{
NSLog(#"Not Found");
}
else
{
int index = [cars indexOfObject:obj];
[results addObject:[NSNumber numberWithInt:index]];
}
}
Why not use
- (NSIndexSet *)indexesOfObjectsPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate
Or similar?
Depending on your search criteria, something like this perhaps?
NSArray *array = #[#"Maruthi",#"Hyundai", #"Ford", #"Benz", #"BMW", #"Toyota"];
NSString *stringToSearch = #"i";
NSIndexSet *set = [array indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
NSString *string = obj;
if (([string rangeOfString:stringToSearch].location == NSNotFound))
{
return NO;
}
return YES;
}];

Unable to sort NSMutableArray of Custom Objects

I've got an issue trying to sort an array of custom objects. It's looking as if my arrays aren't even hitting the sorting code, but rather simply just returning the array itself.
I have the following setup:
SearchResult : NSObject
--
Document : SearchResult
Tag : SearchResult
Folder : SearchResult
My code is getting returns as SearchResults then trying to compare them all with a key, name that is defined in the SearchResult implementation.
-(void) parseFolderContents:(NSDictionary *) data
{
NSMutableArray *searchResults = [[NSMutableArray alloc] init];
NSMutableArray *documents = [[NSMutableArray alloc] init];
NSMutableArray *folders = [[NSMutableArray alloc] init];
NSMutableArray *tags = [[NSMutableArray alloc] init];
NSArray *results = [data objectForKey:#"items"];
for (int i = 0; i < [results count]; i++)
{
SearchResult *result = (SearchResult *)[KTParser parseSearchResult:[results objectAtIndex:i]];
if ([result.type isEqualToString:#"document"]){
[documents addObject:result];
}
else if ([result.type isEqualToString:#"folder"])
{
[folders addObject:result];
}
else if ([result.type isEqualToString:#"tag"])
{
[tags addObject:result];
}
}
if ([documents count] > 0)
[searchResults addObject:documents];
if ([folders count] > 0)
[searchResults addObject:folders];
if ([tags count] > 0)
[searchResults addObject:tags];
....
So that's the code used to populate the array, which isn't anything special. I have tried each of these ways to compare the array. None have worked. Does anyone know where I'm going wrong?
First attempt:
NSSortDescriptor *sortDescriptor;
sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name"
ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedResults;
sortedResults = [searchResults sortedArrayUsingDescriptors:sortDescriptors];
Second attempt:
NSArray *sortedResults;
sortedResults = [searchResults sortedArrayUsingSelector:#selector(compare:)];
(implementing custom compare method on SearchResult/Document.m)
- (NSComparisonResult)compare:(SearchResult *)otherResult{
return [self.name compare:otherResult.name];
}
Third attempt:
sortedResults = (NSMutableArray *)[searchResults sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)]]];
Fourth attempt I tried using a block. I even tried putting code in to manipulate the sorting of it, which didn't work either. The array returned was exactly the same as the original:
sortedArray = [searchResults sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
if ([(SearchResult *)a itemId] < 20000 )
return NSOrderedAscending;
else
return NSOrderedDescending;
}];
Anyone have any ideas?
Found out the issue - I was adding arrays as objects instead of adding each object into the array. This was then calling sort on NSArray, instead of my custom objects. Changing the above code from [searchResults addObject:documents]; to [searchResults addObjectsFromArray:documents]; solved the issue!

Fast way to search the properties of objects in an NSArray

I have an NSArray of custom objects that all have a #property name of type NSString. How can I quickly enumerate through the array and create a new array that contains only the objects that have a specific word in their name property?
For instance:
CustomObject *firstObject = [[CustomObject alloc] init];
firstObject.name = #"dog";
CustomObject *secondObject = [[CustomObject alloc] init];
secondObject.name = #"cat";
CustomObject *thirdObject = [[CustomObject alloc] init];
thirdObject.name = #"dogs are fun";
NSMutableArray *testArray = [NSMutableArray arrayWithObjects:firstObject,
secondObject,
thirdObject,
nil];
// I want to create a new array that contains all objects that have the word
// "dog" in their name property.
I know I could use a for loop like so:
NSMutableArray *newArray = [NSMutableArray array];
for (CustomObject *obj in testArray)
{
if ([obj.name rangeOfString:#"dog"].location == NSNotFound) {
//string wasn't found
}
else {
[newArray addObject:obj];
}
}
But is there a more efficient way? Thanks!
NSString *searchString = #"dog";
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.name contains %#", searchString];
NSArray *filteredArray = [testArray filteredArrayUsingPredicate:predicate];
Please have a look at NSPredicates ! They are highly efficient when you are searching / filtering through array results. This is the documentation!

Different Keys Point to Same Object(s) in NSMutableDictionary

I have a custom object called Person that among other things contains an NSString field called descriptor, which stores what sort of person that Person object is (angry, sad, wild, happy, morose, etc). All of my Person objects are in an NSMutableArray, but I would like to store them in an NSMutableDictionary in such a manner:
Key: A, Object: An NSMutableArray where all Person objects have descriptor starting with 'A'
Key: B, Object: An NSMutableArray where all Person objects have descriptor starting with 'B'
Key: C, Object: An NSMutableArray where all Person objects have descriptor starting with 'C'
etc...
I've tried to do this in my code below, and at the comment //POINT 1, the keys and arrays seem to match up, but at //POINT 2, when I print out the complete dictionary, all the keys come up with the same values!
So I wanted to know why the NSMutableArray I seem to have is not being stored as I want it in the NSMutableDictionary?
- (void)buildDictionaryForIndexList {
NSMutableDictionary *tempDict = [[[NSMutableDictionary alloc] init] autorelease];
NSMutableArray *personsStartingWithLetter = [[NSMutableArray alloc] init];
NSMutableArray *indexList = [[[NSMutableArray alloc] init] autorelease];
NSInteger loopCounter = 1;
NSString *firstLetter = [[[NSString alloc] init] autorelease];
for (Person *v in persons) {
firstLetter = [[v descriptor] substringWithRange:NSMakeRange(0, 1)];
if ([indexList containsObject:firstLetter]) {
[personsStartingWithLetter addObject:v];
if (loopCounter == [persons count]) {
[tempDict setObject:personsStartingWithLetter forKey:firstLetter];
}
} else {
if (loopCounter > 1) {
//POINT 1
NSLog(#"%#",[indexList objectAtIndex:[indexList count]-1]);
for (Person *q in personsStartingWithLetter) {
NSLog(#"%#",[q descriptor]);
}
[tempDict setObject:personsStartingWithLetter forKey:[indexList objectAtIndex:([indexList count] - 1)]];
[personsStartingWithLetter removeAllObjects];
}
[indexList addObject:firstLetter];
[personsStartingWithLetter addObject:v];
} // else
loopCounter++;
} // for
//POINT 2
NSEnumerator *enumerator = [tempDict keyEnumerator];
for (NSString *str in enumerator) {
NSLog(#"%#",str);
for (Person *c in [tempDict objectForKey:str]) {
NSLog(#"%#",[c descriptor]);
}
}
self.dictionary = tempDict;
} // buildDictionaryForIndexList
So, for example, at POINT 1 my output is:
A
Angry
Amiable
B
Belligerent
C
Cool
...
W
Wild
but at POINT 2 my output is
T
Wild
J
Wild
A
Wild
...
W
Wild
Change [tempDict setObject:personsStartingWithLetter forKey:[indexList objectAtIndex:([indexList count] - 1)]]; (just after point 1) to [tempDict setObject:[[personsStartingWithLetter copy] autorelease] forKey:[indexList objectAtIndex:([indexList count] - 1)]];. The problem is that NSDictionary copies the key, but retains the value. Therefore, if you add a mutable array to the dictionary and then change it, the array in the dictionary also changes. You need to create a non-mutable copy of the array to put in the dictionary.
The whole method is a bit overcomplicated.
- (void)buildDictionaryForIndexList
{
NSMutableDictionary *tempDict = [[[NSMutableDictionary alloc] init] autorelease];
for (Person *v in persons)
{
NSString* firstLetter = [[v descriptor] substringWithRange:NSMakeRange(0, 1)];
NSMutableArray* personsStartingWithLetter = tempDict [firstLetter];
if (personsStartingWithLetter == nil)
{
personsStartingWithLetter = [NSMutableArray array];
tempDict [firstLetter] = personsStartingWithLetter;
}
[personsStartingWithLetter addObject:v];
} // for
self.dictionary = tempDict;
}
You start with an empty dictionary that will contain arrays. For every person, you check whether there is a suitable array or not, and if there isn't one, you create it. So now there is an array for the person, so you add it to the array. That's all.