NSPredicate and arrays - cocoa

I've got a short question. I have an NSArray filled with Cars (which inherits from NSObject). Car has the #property NSString *engine (also regarded #synthesize)
Now I want to filter the array using NSPredicate:
predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"(engine like %#)", searchText]];
newArray = [ArrayWithCars filteredArrayUsingPredicate:predicate];
This throws an valueForUndefinedKey error. Is the predicateWithFormat correct?
Thanks for your responses.

Here's the real reason why it's not working:
When you build a string using stringWithFormat:, it's going to come up looking like this:
#"engine like searchText"
You then pass it to predicateWithFormat:, which is going to see that both the lefthand side and the righthand side of the comparison are unquoted strings, which means it's going to interpret them as if they were keypaths.
When this code runs, it's going to be doing:
id leftValue = [object valueForKey:#"engine"];
id rightValue = [object valueForKey:#"searchText"];
return (leftValue is like rightValue);
The exception getting thrown is your object complaining that it doesn't have a method named "searchText", which is correct.
Contrast this to if you took out the stringWithFormat: call and just put everything directly into predicateWithFormat::
NSPredicate *p = [NSPredicate predicateWithFormat:#"engine like %#", searchText];
predicateWithFormat: is smart. It sees "aha, a %# replacement pattern! this means I can pop an argument off the argument list and box it up and use it as-is". That's exactly what it's going to do. It's going to pop the value of searchText off the argument list, box it in an NSExpression, and insert it directly into the parsed expression tree.
What's most interesting here is that the expression it creates will be a constant value expression. This means it's a literal value; not a key path, not a collection, etc. It will be the literal string. Then when your predicate runs, it's going to do:
id leftValue = [object valueForKey:#"engine"];
id rightValue = [rightExpression constantValue];
return (leftValue is like rightValue);
And that is correct, and is also why you should not pass stuff through stringWithFormat: before passing it to predicateWithFormat:.

k, I found the mistake.
predicate = [NSPredicate predicateWithFormat:#"engine like *%#*", searchText];
works correct. The ** were missing. Additionally your searchText should be uppercase.
#Josuhua
this is no real code, just to visualize my problem

First, your code is more verbose than necessary, which always opens you up to the possibility that it's wrong. Try:
predicate = [NSPredicate predicateWithFormat:#"engine like %#", searchText];
Second, "ArrayWithCars" looks like a class name (by convention, classes begin with upper-case). Is it actually a class or an improperly-named local variable (ex: "arrayWithCars" or just "cars")?
Third, what is the exact error? What key is undefined? Don't paraphrase errors when asking others for help - we can't see what you're seeing unless you show us.

Try with this link.You may know different ways of using NSPredicate

Related

Formatting NSPredicate for Photokit Fetch

I'm having a lot of trouble understanding how to formulate filers for photokit queries, specifically, how to formulate an NSPredicate when you only want to retrieve certain mediaTypes. In my case, I'm trying to retrieve all albums (i.e. PHCollectionAssets), though I only want the photos in those albums. I'm confused as to how to formulate my NSPredicate to do this. I found the code below in the docs that filters certain mediaSubtypes, but I just want to filter by the PHAssetMediaTypeImage mediaType. Would someone be able to explain the syntax of the NSPredicate code below and how I could set up a predicate along the lines of #"mediaType == %#, PHAssetMediaTypeImage", that doesn't cause my PHFetchRequest to crash, as it does now?
PHFetchOptions *fetchOptions = [PHFetchOptions new];
fetchOptions.predicate =
[NSPredicate predicateWithFormat:
#"(mediaSubtype & %d) != 0 || (mediaSubtype & %d) != 0",
PHAssetMediaSubtypePhotoPanorama, PHAssetMediaSubtypeVideoHighFrameRate];
PHFetchResult *fetchResult = [PHAsset fetchAssetsWithOptions:fetchOptions];
It seems that there is a subtle difference : PHAssetMediaSubtype is a bit mask value, while PHAssetMediaType is just an int. It doesn't really change their nature but maybe the predicate interpreter simply refuses to apply the bitwise AND operator & to PHAssetMediaType. For that one you need to use the regular equality operator ==.
So, if fetchAssetsWithMediaType is not good enough for you because you want only assets in a given collection and there is no fetchAssetsInAssetCollection:withMediaType: method, you can do that :
PHFetchOptions *fetchOptions = [PHFetchOptions new];
fetchOptions.predicate =
[NSPredicate predicateWithFormat:#"mediaType == %d", PHAssetMediaTypeImage];
PHFetchResult *assetsFetchResult =
[PHAsset fetchAssetsInAssetCollection:anAssetCollection options:fetchOptions];
The example you give is from the docs. It's filtering the PHAssets by their mediaSubtype, which is of type PHAssetMediaSubtype, an enum. You use the logical AND operator to test whether the asset's mediaSubtype is of one or more of the possible subtypes. The example tests whether the PHAssets to be returned from the fetch are either panorama photos (PHAssetMediaSubtypePhotoPanorama) or high frame rate videos (PHAssetMediaSubtypeVideoHighFrameRate).
If you want to fetch only images, you need to do that. Fetch images. Don't fetch all assets and filter by mediaSubtype, as it is unnecessary.
Try the method:
+ (PHFetchResult *)fetchAssetsWithMediaType:(PHAssetMediaType)mediaType options:(PHFetchOptions *)options;
like so:
PHFetchResult *result = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:nil];
This will return all images. Optionally, pass PHFetchOptions to further filter the images returned by this fetch.

iPhone SDK: Append NSString and Count Data

The web service that I am accessing returns this values:
A1;A2;A3;A4;A5;B2;B10;B11;B12;
I want to get the total count of all the data returned separated by ;. How can I do this? I'm thinking of doing a loop inside the string, get the value before ; but getting confused on how to properly run the loop.
There are many string functions and you can use one of them, that is componentsSeparatedByString.
Considering that above mentioned return value is a NSString.
NSString *strResponse = #"A1;A2;A3;A4;A5;B2;B10;B11;B12;";
NSArray *arCount = [strResponse componentsSeparatedByString:#";"];
NSLog(#"Total objects in response is: %d", [arCount count]);

predicateWithFormat vs stringWithFormat: is the former supposed to do the same as the latter?

I have an array with file names and I want to find all the names that end with e.g. 00001.trc when traceNum is 1. I tried this:
NSPredicate *tracePredicate = [NSPredicate predicateWithFormat:#"SELF ENDSWITH \"%05d.trc\"", traceNum];
and my predicate was SELF ENDSWITH "%05d.trc" instead of SELF ENDSWITH "00001.trc"
I tried this:
NSPredicate *tracePredicate = [NSPredicate predicateWithFormat:#"SELF ENDSWITH %#%05d.trc%#", #"\"", traceNum, #"\""];
and I got an exception: Unable to parse the format string "SELF ENDSWITH %#%05d.trc%#".
So I tried this:
NSPredicate *tracePredicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"SELF ENDSWITH \"%05d.trc\"", traceNum]];
and it works.
So do I really need stringWithFormat in addition to predicateWithFormat or is there something I'm not doing correctly in creating my predicate?
You're correct; predicateWithFormat: is not quite the same as stringWithFormat:.
It's different for a couple major reasons:
It's not actually creating a new string. It's just looking through the format string and seeing what the next thing to substitute is, popping that off the va_list, and boxing it up into the appropriate NSExpression object.
It has to support a format specifier that NSString doesn't: %K. This is how you substitute in a key path. If you tried to substitute in the name of a property using %#, it would actually be interpreted as a literal string, and not as a property name.
Using formatting constraints (I'm not sure what the proper term is) like the 05 in %05d isn't supported. For one, it doesn't make sense. NSPredicate does numerical comparisons (in which case 00005 is the same thing as 5, and thus the zero padding is irrelevant) and string comparisons (in which you can just format the string yourself before giving it to NSPredicate). (It does other comparisons, like collection operations, but I'm skipping those for now)
So, how do you do what you're trying to do? The best way would be like this:
NSString *trace = [NSString stringWithFormat:#"%05d.trc", traceNum];
NSPredicate *p = [NSPredicate predicateWithFormat:#"SELF ENDSWITH %#", trace];
This way you can do all the formatting you want, but still use the more correct approach of passing a constant string as the predicate format.

Possible FMDatabase/FMResultSet bug

Even though I rarely have problems with FMDatabase, I noticed some odd behavior today and was wondering if this is a bug or due to a mistake of my own.
NSString *query = [NSString stringWithFormat:#"SELECT * FROM TABLE_A WHERE modelId = %lu", modelId];
FMResultSet *resultSet = [db executeQuery:query];
while ([resultSetIPTCProperties next]) {
NSLog(#"MODEL ID: %lu", [resultSetIPTCProperties intForColumn:#"stringId"]);
}
The odd thing is that this all works fine, but I wanted to play safe and precede the while loop with an if statement using [db hasAnotherRow], but this returns NO even when the result set does contain results.
When I log the resulting dictionary (using FMResultSet's resultDict method) to the console, I get a warning from FMResultSet saying "Warning: There seem to be no columns in this set." even though I can use them in my while loop.
Am I missing something here?
You have to call [resultSet next] before you can call [resultSet resultDict] otherwise the pointer in the result is before the first row. This is also why your loop works, but your check for hasAnotherRow does not.

generating an objectForKey from an array

I'm having success when I use this code to get a string from an array of file names called "fileList":
cell.timeBeganLabel.text = [[[self.fileList objectAtIndex:[indexPath row]] lastPathComponent] stringByDeletingPathExtension];
so I expected the same code to generate the same string as a key for me in this:
NSDictionary *stats = [thisRecordingsStats objectForKey:[[[self.fileList objectAtIndex:[indexPath row]] lastPathComponent] stringByDeletingPathExtension]];
cell.durationLabel.text = [stats objectForKey:#"duration"];
or this:
NSDictionary *stats = [thisRecordingsStats objectForKey:#"%#",[[[self.fileList objectAtIndex:[indexPath row]] lastPathComponent] stringByDeletingPathExtension]];
Both build without error, and the log shows my data is there: but I'm getting a blank UILabel.
Have I not written the dynamic key generator correctly?
I'm having success when I use this code to get a string from an array of file names called "fileList":
cell.timeBeganLabel.text = [[[self.fileList objectAtIndex:[indexPath row]] lastPathComponent] stringByDeletingPathExtension];
So, the result of that message expression is your key, right?
That is to say, the keys in your dictionary are filenames without extensions?
so I expected the same code to generate the same string as a key for me in this:
NSDictionary *stats = [thisRecordingsStats objectForKey:[[[self.fileList objectAtIndex:[indexPath row]] lastPathComponent] stringByDeletingPathExtension]];
cell.durationLabel.text = [stats objectForKey:#"duration"];
You compute the filename without extension as before.
You look up the object for this string in the thisRecordingsStats dictionary, thus obtaining another dictionary, with which you initialize the stats variable.
You look up the object for the “duration” key in the stats dictionary, and set the durationLabel's text to this object.
or this:
NSDictionary *stats = [thisRecordingsStats objectForKey:#"%#",[[[self.fileList objectAtIndex:[indexPath row]] lastPathComponent] stringByDeletingPathExtension]];
Adding the #"%#", part doesn't make sense, since objectForKey: doesn't take a format string. Compare the documentation for NSString's stringWithFormat: method to the documentation for NSDictionary's objectForKey: method.
The code “works” because what you have passed as the argument to objectForKey: is a comma expression. C's comma operator evaluates both sides and evaluates to the right-hand side. However, in this case as in most others, it adds nothing. For reasons like this, the comma operator is rarely used and even more rarely used on purpose.
Cut the #"%#", part out.
Back to the problem:
Both build without error, and the log shows my data is there: but I'm getting a blank UILabel.
Well, you say the key you're generating from the string in your fileList array shows up in the UILabel, so the problem is one of these:
thisRecordingStats is nil.
thisRecordingStats does not contain an object for the key you generated from the string in self.fileList.
thisRecordingStats does contain an object for the key you generated from the string in self.fileList, and it is a dictionary, but it does not contain a value for the key “duration”.
thisRecordingStats does contain an object for the key you generated from the string in self.fileList, and it is a dictionary, and it contains a value for the key “duration”, but that value is an empty (zero-length) string.
You should also check the Debugger Console for messages that suggest other problems. For example, a “does not respond to selector” message may be because thisRecordingStats contains an object for the key you generated from the string in self.fileList, but it is not a dictionary.
Finally, I suggest constructing one or more model object classes instead of nesting dictionaries like this. It tends to make the code much easier to read and to debug. In particular, the dictionaries that ostensibly have objects for the key “duration” should be model objects.

Resources