I've got an NSMutableArray filled with objects of type "GameObject". GameObject has a number of properties, one of which being "gameObjectType" . "gameObjectType" is of type GameObjectTypeEnum. I want to be able to filter this NSMutableArray so only GameObjects of a certain type are returned. I've got the following in place, but it's giving me a "BAD ACCESS" error:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"gameObjectType = %#", gameObjectType];
return [gameObjects filteredArrayUsingPredicate:predicate];
Is it possible to pass a "custom" type (ie, this enum I've defined) into the predicateWithFormat call?
The string format specifier %# indicates an object, while you're passing an integral value. You probably want to typecast the gameObjectType to an int and use the %d specifier. Take a look at the string format specifiers for more info.
- (NSArray *)arrayFilteredByType:(enumType)type {
//type is an NSUInteger property of the objects in the array
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"type = %d", type];
return [self.array filteredArrayUsingPredicate:predicate];
}
Related
I have a NSManagedObject with the following attributes:
status
kind
priority
Now I want to be able to filter my entity with these attributes respectively. So I would expect that I have to have a predicate along those lines:
status CONTAINS[c] ‘open’
I get really weird results, as soon as I have two variables in my predicate and I have to reverse the order of kind and value in my case so that I get the desired results:
NSString *kind = #"status"; // DEBUGGING
NSString *value = #"open"; // DEBUGGING
// This works although it defies all logic
NSString *predicate = [NSString stringWithFormat:#"('%#' CONTAINS[c] %#)", value, kind];
self.myFilterPredicate = [NSPredicate predicateWithFormat:predicate];
This however, does not work for some reason:
NSString *predicate = [NSString stringWithFormat:#"(%# CONTAINS[c] ‘%#‘)”, kind, value];
I cannot reproduce the exact problem, but generally you should not use stringWithFormat to create predicate. It causes problems as soon as the substituted key or value contain
any special characters like spaces or quotation marks.
A better way is
self.myFilterPredicate = [NSPredicate predicateWithFormat:#"%K == %#", kind, value];
%K is a placeholder to be replaced by a key path such as "status".
I have entities: Language, Proper and Answer.
model look like Language{A:name(NSString), R:propers(NSSet)} --->> Proper{A:name(NSString), R:answer(Answer)} ---> Answer{A:answer(NSString)}
So, i got NSDictionary with params: {#"key1", #"value1"}, {#"key2", #"value2"}... i
I need create NSPredicate from this dictionary to get all Languages where propers.name = key[i] and propers.answer.answer = value[i] from my NSDictionary.
Example:
C++
level : high
try/catch : yes
typization : static
Java
level : high
try/catch : yes
typization : dynamic
NSDictionary : {level : hight}, {try/catch : yes}, {typization : dynamic}
//make and set NSPredicate to array controller
//array controller arrangedObjects will return Java
Sorry for bad grammar :/
Update
*After 2 weeks of sleepless nights and work on expert system teacher took a laboratory without checking it. Kill me please. Thanks a lot to all of you.*
I am just guessing what you want to do so here is a code:
- (NSPredicate *)constructPredicateWithDictionary:(NSDictionary *)dictionary
{
NSArray *allKeys = [dictionary allKeys];
NSMutableArray *predicates = [NSMutableArray array];
for (NSString *key in allKeys) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(B, $B, $B.key = %# && $B.C.value = %#).#count > 0", key, [dictionary valueForKey:key]];
[predicates addObject:predicate];
}
//not quite sure what you need so I am guessing
NSPredicate *finalAndPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicates]; //if you want all the predicates to be concatenated with and '&&' - logical expression - so all of the subqueries have to be correct
NSPredicate *finalOrPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:predicates]; //if you want all the predicates to be concatenated with or '||' - logical expression - so any of the subqueries has to be correct
return finalOrPredicate; //return the needed predicate
}
You need from SUBQUERY. Something like:
[NSPredicate predicateWithFormat:#"B.key = %# AND SUBQUERY(B, $B, $B.C.value = %#).#count > 0", key, value];
The SUBQUERY iterates through the objects and you can also have nested SUBQUERYs if you have more than one to-many relationships.
You can use and "ANY ..." but it doesn't work in all cases.
I have a (to me) curious case with NSPredicate's predicateWithFormat: method.
Using the following I log the description of two NSPredicate instances to the console:
NSNumber *myNumber = [NSNumber numberWithInt:1];
NSString *predicateFormatByHand = [NSString stringWithFormat:#"self MATCHES 'chp%#_img[0-9]+\\.png'", myNumber];
NSPredicate *firstPredicate = [NSPredicate predicateWithFormat:predicateFormatByHand];
NSLog(#"firstPredicate description: %#", firstPredicate);
NSPredicate *secondPredicate = [NSPredicate predicateWithFormat:#"self MATCHES 'chp%#_img[0-9]+\\.png'", myNumber];
NSLog(#"secondPredicate description: %#", secondPredicate);
This outputs:
firstPredicate description: SELF MATCHES "chp1_img[0-9]+.png"
secondPredicate description: SELF MATCHES "chp%#_img[0-9]+.png"
I would expect these descriptions to be the same.
Can someone explain why they are not?
(Following this question I've played with various escape sequences for the embedded single-quotes but when doing so keep having NSPredicate complain that it cannot then parse the format string. I'd be grateful to know what's going on.)
UPDATE: one answer suggested it's an issue with using NSNumber rather than an int, so:
NSPredicate *thirdPredicate = [NSPredicate predicateWithFormat:#"self MATCHES 'chp%d_img[0-9]+\\.png'", [myNumber intValue]];
NSLog(#"thirdPredicate description: %#", thirdPredicate);
I began with this originally, but alas the output is the same:
thirdPredicate description: SELF MATCHES "chp%d_img[0-9]+.png"
(Something means the format specifier is not evaluated.)
The answer is simple: the parser used by NSPredicate assumes that anything inside the quote marks is a string literal, and does not attempt to do any substitutions on its contents. It you need to have a dynamic string value, you will have to build the string before substituting it into the predicate format string, as in your first example.
...because the predicate is not such thing than string.
for any of the predicates you should use two format specifier 100% safety only:
one for the key (%K); and
one for the value (%#);
you cannot format neither the key nor the value when you add them to the predicate. this is why your second (and third) predicates are not formatted inside the value.
you can format the value before you add it to the predicate like:
NSNumber *myNumber = [NSNumber numberWithInt:1];
NSString *string = [NSString stringWithFormat:#"chp%#_img[0-9]+\\.png", myNumber];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", string];
NSLog(#"%#", predicate);
the result is:
SELF MATCHES "chp1_img[0-9]+\\.png"
...and never forget my first sentence: the predicates and the strings are not the same thing.
My interpretation of
%# is a var arg substitution for an object value
in the "Predicate Programming Guide" is that %# can only be used for substituting a value that a Core Data object can be compared against. For example
NSNumber *myNumber = [NSNumber numberWithInt:1];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"count = %#", myNumber];
is perfectly valid if "count" is a Number attribute of the entity. It is similar to binding values to SQLite prepared statements.
If %# could be used for general string formatting in predicates, then there would be no need to have two different format specifiers %K and %# for key paths and values.
I have an array which has several objects (all of different classes) in it. But using enumeration doesn't work on it for some reason.
NSString *arrayString;
NSURL *arrayUrl;
NSProcessInfo *arrayPr;
NSDictionary *arrayDictionary;
NSMutableString *arrayMString;
NSMutableArray *objectArray = [NSMutableArray arrayWithObjects:arrayString,arrayUrl,arrayPr,arrayDictionary,arrayMString,nil];
for( NSString *item in objectArray ){
NSLog(#"Class name is: %#", [item className]);
}
I think it might be something to do with how the objects are been added to the array but i'm new to objective-c and not sure.
you aren't actually populating the array.
NSString *arrayString;
declares a variable, arrayString, of type NSString. it's not initialized (so it deserves to crash when you use the variable -- but may be 0 with some build settings).
so, to assign a variable:
NSString *arrayString = [NSString stringWithFormat:#"sksjdhf %f\n", 3.3];
arrayWithObjects adds objects from the (va list) argument until nil/null/0 is encountered.
you must set up the remainder of your variables/arguments correctly before using them.
this should work as you expect it to:
NSString * str = #"a string";
NSMutableArray *objectArray = [NSMutableArray arrayWithObjects:str, nil];
for (NSObject * item in objectArray) {
NSLog(#"Class name is: %#", [item className]);
}
In the for loop, use an id data type. The id data type is a general purpose data type that can be used to store a reference to any object.
For example:
for ( id item in objectArray ) {
NSLog(#"Class name is: %#", [item className]);
}
Yep, that's how you do it. If you're having trouble, it is not in the enumeration syntax itself.
I have an NSArray and I'd like to create a new NSArray with objects from the original array that meet certain criteria. The criteria is decided by a function that returns a BOOL.
I can create an NSMutableArray, iterate through the source array and copy over the objects that the filter function accepts and then create an immutable version of it.
Is there a better way?
NSArray and NSMutableArray provide methods to filter array contents. NSArray provides filteredArrayUsingPredicate: which returns a new array containing objects in the receiver that match the specified predicate. NSMutableArray adds filterUsingPredicate: which evaluates the receiver’s content against the specified predicate and leaves only objects that match. These methods are illustrated in the following example.
NSMutableArray *array =
[NSMutableArray arrayWithObjects:#"Bill", #"Ben", #"Chris", #"Melissa", nil];
NSPredicate *bPredicate =
[NSPredicate predicateWithFormat:#"SELF beginswith[c] 'b'"];
NSArray *beginWithB =
[array filteredArrayUsingPredicate:bPredicate];
// beginWithB contains { #"Bill", #"Ben" }.
NSPredicate *sPredicate =
[NSPredicate predicateWithFormat:#"SELF contains[c] 's'"];
[array filteredArrayUsingPredicate:sPredicate];
// array now contains { #"Chris", #"Melissa" }
There are loads of ways to do this, but by far the neatest is surely using [NSPredicate predicateWithBlock:]:
NSArray *filteredArray = [array filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) {
return [object shouldIKeepYou]; // Return YES for each object you want in filteredArray.
}]];
I think that's about as concise as it gets.
Swift:
For those working with NSArrays in Swift, you may prefer this even more concise version:
let filteredArray = array.filter { $0.shouldIKeepYou() }
filter is just a method on Array (NSArray is implicitly bridged to Swift’s Array). It takes one argument: a closure that takes one object in the array and returns a Bool. In your closure, just return true for any objects you want in the filtered array.
Based on an answer by Clay Bridges, here is an example of filtering using blocks (change yourArray to your array variable name and testFunc to the name of your testing function):
yourArray = [yourArray objectsAtIndexes:[yourArray indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [self testFunc:obj];
}]];
If you are OS X 10.6/iOS 4.0 or later, you're probably better off with blocks than NSPredicate. See -[NSArray indexesOfObjectsPassingTest:] or write your own category to add a handy -select: or -filter: method (example).
Want somebody else to write that category, test it, etc.? Check out BlocksKit (array docs). And there are many more examples to be found by, say, searching for e.g. "nsarray block category select".
Assuming that your objects are all of a similar type you could add a method as a category of their base class that calls the function you're using for your criteria. Then create an NSPredicate object that refers to that method.
In some category define your method that uses your function
#implementation BaseClass (SomeCategory)
- (BOOL)myMethod {
return someComparisonFunction(self, whatever);
}
#end
Then wherever you'll be filtering:
- (NSArray *)myFilteredObjects {
NSPredicate *pred = [NSPredicate predicateWithFormat:#"myMethod = TRUE"];
return [myArray filteredArrayUsingPredicate:pred];
}
Of course, if your function only compares against properties reachable from within your class it may just be easier to convert the function's conditions to a predicate string.
NSPredicate is nextstep's way of constructing condition to filter a collection (NSArray, NSSet, NSDictionary).
For example consider two arrays arr and filteredarr:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains[c] %#",#"c"];
filteredarr = [NSMutableArray arrayWithArray:[arr filteredArrayUsingPredicate:predicate]];
the filteredarr will surely have the items that contains the character c alone.
to make it easy to remember those who little sql background it is
*--select * from tbl where column1 like '%a%'--*
1)select * from tbl --> collection
2)column1 like '%a%' --> NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains[c] %#",#"c"];
3)select * from tbl where column1 like '%a%' -->
[NSMutableArray arrayWithArray:[arr filteredArrayUsingPredicate:predicate]];
I hope this helps
Checkout this library
https://github.com/BadChoice/Collection
It comes with lots of easy array functions to never write a loop again
So you can just do:
NSArray* youngHeroes = [self.heroes filter:^BOOL(Hero *object) {
return object.age.intValue < 20;
}];
or
NSArray* oldHeroes = [self.heroes reject:^BOOL(Hero *object) {
return object.age.intValue < 20;
}];
The Best and easy Way is to create this method And Pass Array And Value:
- (NSArray *) filter:(NSArray *)array where:(NSString *)key is:(id)value{
NSMutableArray *temArr=[[NSMutableArray alloc] init];
for(NSDictionary *dic in self)
if([dic[key] isEqual:value])
[temArr addObject:dic];
return temArr;
}
Another category method you could use:
- (NSArray *) filteredArrayUsingBlock:(BOOL (^)(id obj))block {
NSIndexSet *const filteredIndexes = [self indexesOfObjectsPassingTest:^BOOL (id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
return block(obj);
}];
return [self objectsAtIndexes:filteredIndexes];
}