Cast an NSDictionary value while applying an NSPredicate? - cocoa

I have an Array of NSDictionary objects.
These Dictionaries are parsed from a JSON file.
All value objects in the NSDictionary are of type NSString, one key is called "distanceInMeters".
I had planned on filtering these arrays using an NSPredicate, so I started out like this:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(distanceInMeters <= %f)", newValue];
NSArray *newArray = [oldArray filteredArrayUsingPredicate:predicate];
I believe this would have worked if the value for the "distanceInMeters" key was an NSNumber, but because I have it from a JSON file everything is NSStrings.
The above gives this error:****** -[NSCFNumber length]: unrecognized selector sent to instance 0x3936f00***
Which makes sense as I had just tried to treat an NSString as an NSNumber.
Is there a way to cast the values from the dictionary while they are being filtered, or maybe a completely different way of getting around this?
Hope someone can help me :)

Try this:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(distanceInMeters.floatValue <= %f)", newValue];

Your problem isn't the predicate; your problem is that you're dealing with NSString objects instead of dealing with NSNumber objects. I would focus my time on changing them to NSNumbers first, and then verify that it's not working.
FYI, the JSON Framework does automatic parsing of numbers into NSNumbers...

Related

NSTreeController - Retrieving selected node

I added Book object in bookController (NSCreeController). Now i want to get stored Book object when i select the row.
- (IBAction)addClicked:(id)sender {
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
// NSTimeInterval is defined as double
NSUInteger indexArr[] = {0,0};
Book *obj = [[Book alloc] init];
NSString *dateString = [NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterNoStyle timeStyle:NSDateFormatterLongStyle];
obj.title = [NSString stringWithFormat:#"New %#",dateString];
obj.filename = [NSString stringWithFormat:#"%d",arc4random()%100000];
[self.booksController insertObject:obj atArrangedObjectIndexPath:[NSIndexPath indexPathWithIndexes:indexArr length:2]];
}
I concede there perhaps could be a better solution--
I am unfamiliar with how NSTreeController works, but I looked a the class reference and noticed that it has a content property, similar to an NSArrayController (Which I am familiar with grabbing specific objects from).
I believe that if the content property is actually of type of some kind of tree data structure, my answer here probably won't work. The class reference says this about content:
The value of this property can be an array of objects, or a
single root object. The default value is nil. This property is
observable using key-value observing.
So this is what I historically have done with the expected results:
NSString *predicateString = [NSString stringWithFormat:NEVER_TRANSLATE(#"(filename == %#) AND (title == %#)"), #"FILENAME_ARGUMENT_HERE", #"TITLE_ARGUMENT_HERE"];
NSArray *matchingObjects = [[self content] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:predicateString]];
Then simply calling -objectAtIndex: will grab you your object. Note that the NSArray will be empty if the object doesn't exist, and if you have duplicate objects, there will be multiple objects in the array.
I also searched for an answer to your question, and found this SO thread:
Given model object, how to find index path in NSTreeController?
It looks pretty promising if my solution doesn't work, the author just steps through the tree and does an isEqual comparison.
If you could (if it's not too much trouble), leave a comment here to let me know what works for you, I'm actually curious :)

avoid duplicate results on Core Data fetch

I have a subclass of the CoreDataTableViewController (subclass of UITAbleViewController dome by the people on Stanford done to link CoreData and TableViews). On this Class, I want to perform a fecth, sorting by an attribute called "definition" and the code which executes it is the following:
- (void)setupFetchedResultsController{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:self.entity];
request.propertiesToFetch=[NSArray arrayWithObject:#"definition"];
request.returnsDistinctResults=YES;
NSPredicate *predicate1 = [NSPredicate predicateWithFormat:#"%K != nil", #"definition"];
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:#"%K != ''", #"definition"];
NSPredicate *predicate3= [NSPredicate predicateWithFormat:#"%K contains[cd] %#", #"definition", self.seachBar.text];
NSArray *prepredicateArray;
if ([self.seachBar.text length]) {
prepredicateArray = [NSArray arrayWithObjects:predicate1, predicate2, predicate3,nil];
}else {
prepredicateArray = [NSArray arrayWithObjects:predicate1, predicate2,nil];
}
request.predicate=[NSCompoundPredicate andPredicateWithSubpredicates:prepredicateArray];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"definition" ascending:YES ]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
[self performFetch];
}
If I understood it correctly, setting request.returnsDistinctResults=YES; should avoid fetching duplicates. However it doesn't work and I'm seeing duplicates of this attribute's value.
Is there something I'm missing there? I'd appreciate some pointings there. Thank you in advance.
EDIT: If anyone is having the same issue here, after applying David's answer the resulting fetchedResultsController is just a NSDIctionary with object with only the requested value, which for displaying only purposes is quite fine. One thing I've done in cellForRowAtIndexPath in order to display the results on the cell label is:
Before:
HNMR *hnmr = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text=hnmr.definition;
After:
cell.textLabel.text=[[self.fetchedResultsController objectAtIndexPath:indexPath] valueForKey:#"definition"];
From the documentation of returnsDistinctResults:
This value is only used if a value has been set for propertiesToFetch.
From the documentation of propertiesToFetch:
This value is only used if resultType is set to NSDictionaryResultType.
From the documentation of resultType:
The default value is NSManagedObjectResultType.
This all tells me that the propertiesToFetch is ignored because you haven't set the resultType yourself and the default it to return managed objects instead of dictionaries. Since the propertiesToFetch is ignored the returnsDistinctResults is ignored as well and thus you are still getting duplicates.
Try setting the result type to return dictionaries instead of managed objects.
request.resultType = NSDictionaryResultType;
In addition to David Rönnqvist answer I suggest a useful link (with a sample) on selecting distinct values with Core Data:
core-data-how-to-do-a-select-distinct
Hope that helps.

how to sort NSMangedObjects by its NSDate attributes

Question is short:
I have an array of some NSManagedObjects,
all of them have an NSDate attribute
Now I want to sort this arry by their date, the latest the first,
how could implement this?
You want to use NSArray's sortedArrayUsingDescriptors: method with an NSSortDescriptor. If your array is called array and the NSDate attribute is called date, then this would work:
NSArray *sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"date" ascending:NO]];
NSArray *sortedArray = [array sortedArrayUsingDescriptors:sortDescriptors];
Also, note that when you're getting these NSManagedObjects using an NSFetchRequest, you can give the request sortDescriptors so that they're already sorted. Using the same sortDescriptors from above, just do the following before executing the request:
request.sortDescriptors = sortDescriptors;

Why doesn't this NSPredicate work?

I have an Array of MKAnnotation objects called arrAnnotations. I want to pick out one of the annotations with the same coordinate as the one stored in a CLLocation object called "newLocation".
I'm trying to use a NSPredicate, but it doesn't work.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(SELF.coordinate == %f)", newLocation.coordinate];
NSArray* filteredArray = [arrAnnotations filteredArrayUsingPredicate:predicate];
[self.mapView selectAnnotation:[filteredArray objectAtIndex:0] animated:YES];
The filteredArray always contains zero objects.
I have also tried the following, which do not work either
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(coordinate == %f)", newLocation.coordinate];
and
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(coordinate > 0)"];
and
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(coordinate.latitude == %f)", newLocation.coordinate.latitude];
The last two crash the app, the third one with an NSInvalidArgumentException for [NSConcreteValue compare:] and the fourth, because latitude is not key-value-coding-compliant (I assume this is because coordinate is just a c-struct and not an NSObject?).
How can I make this work with NSPredicate?
Can anyone give me a link to a document that shows how Predicates work under the hood?
I don't understand what they actually do, even though I have read and understood most of Apple's Predicate Programming Guide.
Is searching a huge array with predicates more efficient than just looping through it with a for...in construct? If yes/no, why?
MKAnnotation protocol's coordinate property is a CLLocationCoordinate2D struct, thus it is not allowed in NSPredicate format syntax according to the Predicate Format String Syntax
You could use NSPredicate predicateWithBlock: instead to accomplish what you are trying to do, but you have be careful with CLLocationCoordinate2D and how to compare it for equality.
CLLocationCoordinate2D's latitude and longitude property are CLLocationDegrees data type, which is a double by definition.
With a quick search you can find several examples of problems you would face when comparing floating point values for equality. Some good examples can be found here, here and here.
Given all that, I believe that using code bellow for your predicate might solve your problem.
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
id<MKAnnotation> annotation = evaluatedObject;
if (fabsf([annotation coordinate].latitude - [newLocation coordinate].latitude) < 0.000001
&& fabsf([annotation coordinate].longitude - [newLocation coordinate].longitude) < 0.000001) {
return YES;
} else {
return NO;
}
}];

Store selector as value in an NSDictionary

Is there a way to store a selector in an NSDictionary, without storing it as an NSString?
SEL is just a pointer, which you could store in an NSValue:
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSValue valueWithPointer:#selector(foo)], #"foo",
nil];
To get the selector back, you can use:
SEL aSel = [[dict objectForKey:#"foo"] pointerValue];
An alternative to Georg's solution would be to convert the selector into an NSString before storing it the NSDictionary:
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromSelector(#selector(foo)), #"foo",
nil];
SEL selector = NSSelectorFromString([dict objectForKey:#"foo"]);
This technique, though uses more memory, gives you the ability to serialize the entire NSDictionary as a string via libraries like JSONKit.
An NSDictionary is really just a CFDictionary that retains and releases all keys and values. If you create a CFDictionary directly, you can set it up to not retain and release values. You can typecast a CFDictionaryRef to an NSDictionary * and vice versa.
In case of using UILocalNotification the only way is to use NSSelectorFromString([dict objectForKey:#"foo"]). With valueWithPointer the app crashing when setting userInfo property of UILocalNotification object. Be careful.
While Georg's answer should work, NSValue does also support encoding any value using an Objective-C type encoding string, which has a special way of representing SEL— with a ":" (as opposed to the "^v" produced by -valueWithPointer:, which translates into void *).
source: Objective-C Runtime Programming Guide - Type Encodings
Working off of Georg's solution, the best API-compliant way to put a SEL into an NSValue into an NSDictionary would be:
// store
NSDictionary *dict = #{
#"foo": [NSValue value:&#selector(foo) withObjCType:#encode(SEL)]
};
// retrieve
SEL aSel;
[dict[#"foo"] getValue:&aSel];
The rationale for handling a SEL as its own beast is that the docs describe it as “an opaque type”— which means that its internal workings (even what it's typedefd to) are off-limits to app programmers; Apple may mix it up at any time in the future.
Also, using void *s to force the system to do what you want it to do was useful in C back in the '90s, when most of us didn't know any better.  You're better than that now.
The above approach should only be used if the retrieval of the SEL happens during the program's running duration— you shouldn't be storing that NSDictionary to disk.  If you do need to store SELs long-term (across app launches), you should follow David H's approach and convert it to an NSString.

Resources