NSPredicate to compare integer using Contains - iphone

I am using a NSPredicate to search numbers in the list using UISearchBar ,
it works in case of strings but does not work for an integer
I am using the following predicate
predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"%# contains[c] %d", #"number", [searchBar.text intValue]]];
[objectArray filterUsingPredicate:predicate];
[tableview reloadData];
FOR example if I type 1 then all the ones in the array must be listed, I have tried == it works only for the exact number if tried any work around for this any body?
Now I get an error if I use this method "Can't use in/contains operator with collection"

I think this predicate should work for you:
predicate = [NSPredicate predicateWithFormat:#"self.number.stringValue CONTAINS %#",searchBar.text];
After thinking about this, I'm not sure why self.number.stringValue works, but it did when I tested it (self.number is an int). Not sure why I can send stringValue to an int?

Predicates can be tricky to work with, so perhaps an alternative would work for you:
NSInteger index = 0;
while (index < objectArray.count)
{
NSString *currentString = [objectArray objectAtIndex:index];
if ([currentString rangeOfString:searchBar.text].length == 0)
{
[objectArray removeObjectAtIndex:index];
continue;
}
index++;
}
Here, any strings in your array that do not contain your searchBar text will be removed.

Related

Code working on Simulator, but not on iPhone?

I am stumped by this: I've tested the filter function of my app in the iPhone Simulator 4.3 and 5.0 and everything works, but on the iPhone the predicate gets me the wrong results. (I suspect it has something to do with the regex, but I don't see an error.)
if (!selectionDidChange)
return;
[matches release];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"meta LIKE[c] %#",
UniversalKeyword];
NSPredicate *regional = [NSPredicate predicateWithFormat:#"regional == NIL OR regional == NO OR "
#"(regional == YES AND title.%K != NIL)", CurrentLanguage];
NSPredicate *exclusive = (exclusiveUpgrade ? [NSPredicate predicateWithValue:YES] :
[NSPredicate predicateWithFormat:#"exclusive == NIL OR exclusive == NO"]);
NSMutableArray *predicates = [[[NSMutableArray alloc] initWithCapacity:5] autorelease];
for (int i = 0; i < L(keywords); i++)
{
int selection = [D(A(keywords, i), #"selection") intValue];
if (selection >= 0)
A_ADD(predicates, ([NSPredicate predicateWithFormat:#"meta MATCHES[c] %#",
S(S(#".*\\b", D(A(D(A(keywords, i), #"values"), selection), #"name")), #"\\b.*")]));
}
NSPredicate *compound = [NSCompoundPredicate andPredicateWithSubpredicates:predicates];
[predicates removeAllObjects];
[predicates addObject:predicate];
[predicates addObject:compound];
predicate = [NSCompoundPredicate andPredicateWithSubpredicates:A_NEW(regional, exclusive,
[NSCompoundPredicate orPredicateWithSubpredicates:predicates])];
matches = [[entries filteredArrayUsingPredicate:predicate] retain];
selectionDidChange = NO;
For clarification:
entries and keywords are arrays of dictionaries, although keywords is a bit more complex. Important is that each dictionary in entries contains a string named meta that can look something like this: "A, B, C, D". And if the user searches for "C", the regex should match. There are other criteria that don't seem to be the problem, since I checked the compiled predicate and it looks fine.
I should mention, the first part of the predicate (meta LIKE[c] %#) gives me the expected result on the iPhone as well.
I have some used convenience macros here: A_ADD = addObject:, D = objectForKey:, A = objectAtIndex:, A_NEW = arrayWithObjects:, L = count and S = stringByAppendingString:. (Yeah, I'm lazy :D )
What am I overlooking here?
Here are the key points in case anybody else has a similar problem:
There are no functional differences between NSPredicate on the iPhone and the corresponding implementation on the iOS Simulator.
If your app behaves differently on the actual device than on the Simulator, double-check file names and other strings for capitalization, like jmstone said.
If the problem persists, remove the app both from the Simulator and from the device. Xcode has many automatic behaviors, but it doesn't clean up anything on the Simulator or the device.

SearchDisplayController search multiple arrays

Currently I'm populating my tableviewcells with the contents of multiple arrays representing a name, id, etc.
My question comes when I start to use the search display controller. I have an array with a list of names, a list of IDs, a list of barcodes, and a list of Aliases. When the user types in the search bar I need to be able to search all 4 arrays. When it finds the result in 1 array it has to pair the result with the 3 other arrays..
Example
Names (apple,carrot,banana, dog)
alias (red, orange, yellow, brown)
barcode (1,2,10,20)
id (30, 40, 50, 60)
So if the user types "a" I should populate the table view with
Apple, Carrot, Banana and the associated alias, barcode, id.
If the user were to type 2 I should only get
carrot and dog.
If the user were to type 0 I would get all of those items.
Any ideas how to accomplish this?
UPDATE:
This is how I did it.
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
BOOL shouldReturn = FALSE;
[searchResults removeAllObjects];
for (int i = 0; i < [itemIDRows count]; i++) {
BOOL foundResult = FALSE;
if ([[itemIDRows objectAtIndex:i] rangeOfString:searchString].location != NSNotFound) {
foundResult = TRUE;
}
if ([[nameRows objectAtIndex:i] rangeOfString:searchString].location != NSNotFound) {
foundResult = TRUE;
}
if ([[barcodeRows objectAtIndex:i] rangeOfString:searchString].location != NSNotFound) {
foundResult = TRUE;
}
if ([[aliasRows objectAtIndex:i] rangeOfString:searchString].location != NSNotFound) {
foundResult = TRUE;
}
if (foundResult) {
NSNumber *result = [NSNumber numberWithInt:i];
if ([self searchResults] == nil) {
NSMutableArray *array = [[NSMutableArray alloc] init];
[self setSearchResults:array];
[array release];
}
[searchResults addObject:result];
shouldReturn = YES;
}
}
return shouldReturn;
}
Then when I'm populating the tableview I do something like this
if ([tableView isEqual:self.searchDisplayController.searchResultsTableView]) {
[cell setCellContentsName:[NSString stringWithFormat:#"%#", [nameRows objectAtIndex:[[searchResults objectAtIndex:indexPath.row] integerValue]]];
} else {
[cell setCellContentsName:[NSString stringWithFormat:#"%#", [nameRows objectAtIndex:indexPath.row]];
}
However when I type something like 9999 it brings up instances where only 1 9 is in the ID or barcode. Any ideas how to fix that?
UPDATE2:
Solved the problem by having the list always refresh instead of only reloading the data if a result was found. Now it works perfectly :D
The search display controller calls the
UISearchDisplayDelegate
method:
searchDisplayController:shouldReloadTableForSearchString:
Inside this method, you need to implement your logic. This logic will need to search all 4 of your arrays for hits, and do the appropriate lookups (i.e. to get from orange to carrot, or from 50 to banana). Each time you get a hit, I would put it in an NSMutableSet (to prevent dupes). Then when you're done searching all arrays, copy the set into the array that your table's data source reads from.
If you want to show the user WHY a given row is a hit (i.e. they typed 50 and got banana), you'd have to display all 4 of the attributes in your table cell. And you'd need to highlight the part that matched. If you do this, I'd create a small container class, something like "searchHit" that contains all 4 attributes, as well as a flag for which attribute got the hit, and possibly the substring of the attribute that got the hit (so you can use a yellow background for this substring, for example.) The tableView's data source would then have an array of these searchHit objects to display, and your cellForRowAtIndexPath would need to decode this object and display the hit appropriately.
You can do that with NSPredicate using KVC object.
Create an NSObject respond to the KVC scheme http://theocacao.com/document.page/161 . You can use property for that.
Filter your array with an NSPredicate http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSPredicate_Class/Reference/NSPredicate.html
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"self.name LIKE[cd] %# OR self.alias LIKE[cd] %#",searchString,searchString];
NSArray *result = [baseArray filteredArrayUsingPredicate:predicate];

NSMutableArray check if object already exists

I am not sure how to go about this. I have an NSMutableArray (addList) which holds all the items to be added to my datasource NSMutableArray.
I now want to check if the object to be added from the addList array already exists in the datasource array. If it does not exist add the item, if exists ignore.
Both the objects have a string variable called iName which i want to compare.
Here is my code snippet
-(void)doneClicked{
for (Item *item in addList){
/*
Here i want to loop through the datasource array
*/
for(Item *existingItem in appDelegate.list){
if([existingItem.iName isEqualToString:item.iName]){
// Do not add
}
else{
[appDelegate insertItem:item];
}
}
}
But i find the item to be added even if it exists.
What am i doing wrong ?
There is a very useful method for this in NSArray i.e. containsObject.
NSArray *array;
array = [NSArray arrayWithObjects: #"Nicola", #"Margherita", #"Luciano", #"Silvia", nil];
if ([array containsObject: #"Nicola"]) // YES
{
// Do something
}
I found a solution, may not be the most efficient of all, but atleast works
NSMutableArray *add=[[NSMutableArray alloc]init];
for (Item *item in addList){
if ([appDelegate.list containsObject:item])
{}
else
[add addObject:item];
}
Then I iterate over the add array and insert items.
Use NSPredicate.
NSArray *list = [[appDelegate.list copy] autorelease];
for (Item *item in addList) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"iName MATCHES %#", item.iName];
NSArray *filteredArray = [list filteredArrayUsingPredicate:predicate];
if ([filteredArray count] > 0) [appDelegate insertItem:item];
}
Did you try indexOfObject:?
-(void)doneClicked{
for (Item *item in addList){
if([appDelegate.list indexOfObject:item] == NSNotFound){
[appDelegate insertItem:item];
}
}
UPDATE: You have a logical mistake, not mistake in code. assume the first array is ['a', 'b', 'c'], and the second is ['a', 'x', 'y', 'z']. When you iterate with 'a' through the second array it won't add 'a' to second array in the first iteration (compare 'a' with 'a') but will add during the second (compare 'a' with 'x'). That is why you should implement isEqual: method (see below) in your 'Item' object and use the code above.
- (BOOL)isEqual:(id)anObject {
if ([anObject isKindOfClass:[Item class]])
return ([self.iName isEqualToString:((Item *)anObject).iName]);
else
return NO;
}
Have a look at NSSet. You can add objects and the object will only be added if the object is unique. You can create a NSSet from an NSArray or vise versa.
You can override isEquals and hash on the object so that it returns a YES / NO based on the comparison of the iName property.
Once you have that you can use...
- (void)removeObjectsInArray:(NSArray *)otherArray
To clean the list before adding all the remaining objects.
NR4TR said correctly but i think one break statement is sufficient
if([existingItem.iName isEqualToString:item.iName]){
// Do not add
break;
}
Convert Lowercase and Trim whitespace and then check..
[string lowercaseString];
and
NSString *trim = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
You compare the addList's first object and appDelegate.list's first object, if they are not equal, you insert the addList's object. The logic is wrong, you should compare one addList's object with every appDelegate.list's object.

Search NSArray for value matching value

I have an NSArray of objects, which has a particular property called name (type NSString).
I have a second NSArray of NSStrings which are names.
I'd like to get an NSArray of all the objects whose .name property matches one of the names in the second NSArray.
How do I go about this, fast and efficiently as this will be required quite often.
Why not just to use predicates to do that for you?:
// For number kind of values:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF = %#", value];
NSArray *results = [array_to_search filteredArrayUsingPredicate:predicate];
// For string kind of values:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains[cd] %#", value];
NSArray *results = [array_to_search filteredArrayUsingPredicate:predicate];
// For any object kind of value (yes, you can search objects also):
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", value];
NSArray *results = [array_to_search filteredArrayUsingPredicate:predicate];
Here's a simple way:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name == %#", nameToFind];
[listOfItems filteredArrayUsingPredicate:predicate];
With your current data structures, you can only do it in O(n^2) time by looping over the first array once for each member of the second array:
NSMutableArray * array = [NSMutableArray array];
for (NSString * name in names) {
for (MyObject * object in objects) {
if ([[myObject name] isEqualToString:name]) {
[array addObject:object];
}
}
}
(Alternate as suggested by Stefan: loop over the objects array and ask the names array if it containsObject: for the name of each object.)
But if this really needs to be faster (really depends on the size of the arrays as much as how often you do it), you can improve this by introducing an NSDictionary that maps the names in the first array to their objects. Then each of those lookups is O(1) and the overall time is O(n). (You'd have to keep this dictionary always in sync with the array of objects, which isn't hard with reasonable accessors. This technique also has the constraint that the same name can't appear on more than one object.)
An alternate way of getting this result (and which doesn't have that last constraint) is to use an NSSet for your second collection, then walk through the objects array calling containsObject: with each one on the set of names. Whether this technique is better depends on whether your two collections are roughly the same size, or if one is much larger than the other.
I like to use this method:
NSIndexSet *indexes = [_items indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return ((MyObject *)obj).name isEqualToString:name];
}];
if (indexes.count != 0) {
//extract your objects from the indexSet, and do what you like...
}
NSMutableArray * foundNames = [NSMutableArray array];
for (MyObject * objectWithName in objectCollection) {
if ([names containsObject:objectWithName.name]) {
[foundNames objectWithName];
}
}
The methods most helpful will be:
filteredArrayUsingPredicate:
and
indexesOfObjectsPassingTest:
The second one uses a code block, not available on iOS before 4.0
Both of these will be more efficient than iterating directly.
There's a good example here:
http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/bxUsing.html
NSMutableArray* solutions = [NSMutableArray array];
for (Object* object in objects){
for (NSString* name in names){
if ([object.name isEqualToString:name]){
[solutions addObject:object];
break; // If this doesnt work remove this
}
}
}
int count=0;
if (range.location!=NSNotFound)
{
[searchindex addObject:[NSString stringWithFormat:#"%d",count]];
}

Testing if NSMutableArray contains a string object

I have a NSMutableArray which contains a few NSString objects. How can I test if the array contains a particular string literal?
I tried [array containsObject:#"teststring"] but that doesn't work.
What you're doing should work fine. For example
NSArray *a = [NSArray arrayWithObjects:#"Foo", #"Bar", #"Baz", nil];
NSLog(#"At index %i", [a indexOfObject:#"Bar"]);
Correctly logs "At index 1" for me. Two possible foibles:
indexOfObject sends isEqual messages to do the comparison - you've not replaced this method in a category?
Make sure you're testing against NSNotFound for failure to locate, and not (say) 0.
[array indexOfObject:object] != NSNotFound
Comparing against string literals only works in code examples. In the real world you often need to compare against NSString* instances in e.g. an array, in which case containsObject fails because it compares against the object, not the value.
You could add a category to your implementation which extends NS(Mutable)Array with a method to check wether it contains the string (or whatever other type you need to compare against);
#implementation NSMutableArray (ContainsString)
-(BOOL) containsString:(NSString*)string
{
for (NSString* str in self) {
if ([str isEqualToString:string])
return YES;
}
return NO;
}
#end
You may also use a predicate:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF IN %#", theArray];
BOOL result = [predicate evaluateWithObject:theString];
for every object
[(NSString *) [array objectAtIndex:i] isEqualToString:#"teststring"];