How to sort NSMutableArray key by date (Facebook Birthdays?) - objective-c

Im trying to use NSSortDescriptor to sort the NSMutableArray *friends, key:birthday, by date, I'm getting null for all the birthdays in my NSLog, however the names are still logging. If I pull out my NSSortDescriptor my NSLog will give me the values again, wondering why i lose the values in passing them through the NSSortDescriptor, and how I can pull this off. Im using the Hackbook sample code to learn Facebook integration, and have modified the code to give me my friends birthdays, and id like to have them sorted January - December in format 1/1 - 12/31 Is it because of the randomizing pull of resultData of a friends list? Thanks you in advance, ill be sure to accept as soon as we get to the bottom of this!
******************************CODE TO PULL KEYS BIRTHDAY AND NAMES FROM GRAPH API ******
- (void)getUserFriends {
currentAPICall = kAPIGraphUserFriends;
HackbookAppDelegate *delegate = (HackbookAppDelegate *)[[UIApplication sharedApplication] delegate];
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
#"name, birthday", #"fields", nil];
[[delegate facebook] requestWithGraphPath:#"me/friends" andParams:params andDelegate:self];
[self apiGraphFriends];
}
***********RANDOMIZE LIST OF FRIENDS NAMES W BIRTHDAYS AND PUSH TO VIEW CONTROLLER
NSMutableArray *friends = [[NSMutableArray alloc] initWithCapacity:1];
NSArray *resultData = [result objectForKey:#"data"];
if ([resultData count] > 0) {
for (NSUInteger i=0; i<[resultData count] && i < 1000; i++) {
[friends addObject:[resultData objectAtIndex:i]];
}
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"birthday"
ascending:TRUE];
[friends sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[sortDescriptor release];
// LOG VALUES OF KEYS birthday and name FOR DEBUGGING
NSLog(#"name %#" , [friends valueForKey:#"name"]);
NSLog(#"birthday %#" , [friends valueForKey:#"birthday"]);
// Show the friend information in a new view controller
NSLog(#"Check#3");
APIResultsViewController *controller = [[APIResultsViewController alloc]
initWithTitle:#"Friends Birthdays"
data:friends action:#""];
[self.navigationController pushViewController:controller animated:YES];
[controller release];
} else {
[self showMessage:#"You have no friends."];
}
[friends release];
8/10 edit added code above that shows how i am getting these names and birthdays, i believe them both to be NSStrings per http://developers.facebook.com/docs/reference/api/user/ Just scroll down to the birthday part. I want to also mention, VERY IMPORTANT, that i have extended permissions, and this is not a permission issue, its something im doing wrong when i try to sort the array, and i believe my inexperience is my own worst enemy here. Thanks!

This may help you identify the problem. In general, this allows you to sort a list with a custom comparator, but it may be useful to determine why sortUsingDescriptors is nulling your birthdays. Perhaps one or more of your birthdays is not valid.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
initWithKey:#"birthday"
ascending:YES
comparator:^NSComparisonResult(id obj1, id obj2) {
NSLog(#"bday1 = '%#' bday2 = '%#'", obj1, obj2);
return NSOrderedSame;
}];

Related

Populating TableView with NSMutableSet

I am using core data and trying to populate a UITableView with an NSMutableSet. I have two entities, Teams and Players. On my addTeamsController I am saving a player to the team as follows
-(void)saveButtonWasPressed {
self.team =[NSEntityDescription insertNewObjectForEntityForName:#"Team" inManagedObjectContext:self.managedObjectContext];
Player *newPlayer = (Player *)[NSEntityDescription insertNewObjectForEntityForName:#"Player"
inManagedObjectContext:self.managedObjectContext];
team.schoolName = _schoolName.text;
team.teamName = _teamName.text;
team.teamID = _teamName.text;
team.season = _season.text;
team.headCoach = _headCoach.text;
team.astCoach = _assistantCoach.text;
[self.team addPlayers:_tempSet];
[self.managedObjectContext save:nil];
[self.navigationController popViewControllerAnimated:YES];
}
On another viewController I am trying to populate a tableview with that teams players. To do that I am doing as follows
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"firstName" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
_array = [[_team.players allObjects] sortedArrayUsingDescriptors:sortDescriptors];
and then on my cell for row and index path I am doing the following
cell.textLabel.text = [_array objectAtIndex:indexPath.row];
And I get the error
[Player isEqualToString:]: unrecognized selector sent to instance
I am wondering what the best approach to filling the tableview sorted by the players first names is.
The best way to populate a table from a core data store is to use an NSFetchedResultController. But that is not going to fix the problem you're having, the problem is that your trying to set cell.textLabel.text to an NSManagedObject, which doesn't work. You can set
cell.textLabel.text = Player.someStringAttribute

Can you use NSSortDescriptor to sort by a value not being null?

I have the following sort descriptors that sort an array of my business objects, ready to be displayed in a table, I'm starting off with some sample sorting code from a previous SO question
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"awardedOn" ascending:NO];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor1, sortDescriptor2, nil];
NSArray *sortedArray = [returnable sortedArrayUsingDescriptors:sortDescriptors];
The objects that I'm displaying all will have a title. Only some of them will have an "awardedOn" set, which is an NSDate.
What I want to do:
Sort entire array so all the objects with an "awardedOn" set are
displayed at the top
Within the two "sets", order them alphabetically
I don't care about the actual value of the date, I'm more interested
if it exists or not
Something like this (Titles, the bold ones have a value for awardedOn)
Awesome
Better
Cool
Another
Another One
One more
Yet Another
You should be able to do that using two descriptors like you first said, first by awardedOn, then by title. However, you need to provide a custom NSSortDescriptor for the awardedOn sort that looks someting like this:
#define NULL_OBJECT(a) ((a) == nil || [(a) isEqual:[NSNull null]])
#interface AwardedOnSortDescriptor : NSSortDescriptor {}
#end
#implementation AwardedOnSortDescriptor
- (id)copyWithZone:(NSZone*)zone
{
return [[[self class] alloc] initWithKey:[self key] ascending:[self ascending] selector:[self selector]];
}
- (NSComparisonResult)compareObject:(id)object1 toObject:(id)object2
{
if (NULL_OBJECT([object1 valueForKeyPath:[self key]])) {
if (NULL_OBJECT([object2 valueForKeyPath:[self key]]))
return NSOrderedSame; // If both objects have no awardedOn field, they are in the same "set"
return NSOrderedDescending; // If the first one has no awardedOn, it is sorted after
}
if (NULL_OBJECT([object2 valueForKeyPath:[self key]])) {
return NSOrderedAscending; // If the second one has no awardedOn, it is sorted after
}
return NSOrderedSame; // If they both have an awardedOn field, they are in the same "set"
}
#end
This will allow you to have to separate sets: Awesome/Better/Cool and Another/Another One/One More/Yet another, in your example. After that, you should be good with:
NSSortDescriptor *sortDescriptor1 = [[AwardedOnSortDescriptor alloc] initWithKey:#"awardedOn" ascending:YES];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:YES];
On a final note, you might need a litte more work depending on what your "empty" awardedOn fields look like (I assumed, in the code above, that the field was set to null). You can take a look here: https://stackoverflow.com/a/3145789
There are two possible ways:
When creating a business object, assign awardedOn date as distantPast if it does not exist and then do normal sorting by awardedOn and then by title.
Create sort descriptor with custom comparison method that will be called on each of business objects:
.
NSSortDescriptor *awardDescriptor = [NSSortDescriptor descriptorWithKey:#"awardedOn"
ascending:NO
selector:#selector(compareAwardedOn:)];
// In class for business object
- (NSComparisonResult)compareAwardedOn:(id)otherBusiness {
// return custom NSComparison result after
// checking whether either of awardedOn dates are nil.
return NSOrderedSame;
}
Try to use comparator:
_finalArray = [_array sortedArrayUsingComparator: ^NSComparisonResult(NSDictionary *obj1, NSDictionary *obj2)
{
if([[obj1 valueForKey#"awardedOn"] lenght] && ![[obj2 valueForKey#"awardedOn"] lenght])
return NSOrderedDescending;
if([[obj2 valueForKey#"awardedOn"] lenght] && ![[obj1 valueForKey#"awardedOn"] lenght])
return NSOrderedAscending;
return NSOrderedSame;
}];

How can I do a case insensitive sort of NSFetchedResultsController that also ignores words like "the"?

I'm working on a Core Data driven app that has 2 entities requiring special sorting considerations in the NSFetchedResultsController.
The simpler case of the 2 just requires ignoring "The" so that (& I'm citing real data examples) The Planting Festival follows Peats Ridge Festival in the sort order. As a safety I'd also like to make that sort case insensitive. For that one I tried the suggestions here How can I sort an NSTableColumn of NSStrings ignoring "The " and "A "? by adding...
#property (nonatomic, readonly) NSString * sortname;
to the interface of my entity definition class &...
- (NSString *)sortname
{
NSMutableString *temp = [NSMutableString stringWithString:[[self name] lowercaseString]];
if ([temp hasPrefix:#"the "]) {
return [temp substringFromIndex:4];
}
return temp;
}
to the implementation but the app crashes when I give it #"sortname" as the key for the NSSortDescriptor.
The 2nd entity is more complicated. In addition to stripping "The" for sorting there is also "A" & honorifics like "Dr" & "Professor" but not always. An example in data is Professor Ian Lowe & Professor Wallace's Puppet Theatre. Ian Lowe is a real person who (much as I respect him) should be sorted under I for Ian but there is no Professor Wallace, that's just the name on a, well, puppet theatre & it should be sorted under P. For that selective aspect of it I see no way other than having a boolean attribute on the entity specifies whether or not to strip the 1st word for sorting purposes.
For the rest of it the question comes down to... Is there a way to create a (for want of knowing a more correct term) virtual attribute on a Core Data entity that will work when I pass its name as the key for an NSSortDescriptor of an NSFetchedResultsController?
The desperate approach, which I'd rather not take, would be to create real sortname attributes on those 2 entities & calculate the values as I populate the entities.
Update...
Attempting to follow TechZen's answer this is the constructor for my NSFetchedResultsController...
- (NSFetchedResultsController *)frc
{
if (_frc) return _frc;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"EventProfile"
inManagedObjectContext:_moc];
[request setEntity:entity];
NSSortDescriptor *sorter = [[NSSortDescriptor alloc] initWithKey:#"name"
ascending:YES
comparator:^NSComparisonResult(id obj1, id obj2) {
NSMutableString *sortname1 = [NSMutableString stringWithString:[obj1 lowercaseString]];
if ([sortname1 hasPrefix:#"the "]) {
sortname1 = [NSMutableString stringWithString:[sortname1 substringFromIndex:4]];
}
NSMutableString *sortname2 = [NSMutableString stringWithString:[obj2 lowercaseString]];
if ([sortname2 hasPrefix:#"the "]) {
sortname2 = [NSMutableString stringWithString:[sortname2 substringFromIndex:4]];
}
return [sortname1 compare:sortname2];
}];
[request setSortDescriptors:[NSArray arrayWithObject:sorter]];
[sorter release], sorter = nil;
[request setFetchBatchSize:15];
NSFetchedResultsController *frcTemp = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:_moc
sectionNameKeyPath:nil
cacheName:[NSString GetUUID]];
[self setFrc:frcTemp];
[_frc setDelegate:self];
[frcTemp release], frcTemp = nil;
[request release], request = nil;
if ([[_frc fetchedObjects] count] == 0) {
//<#statements#>
}
return _frc;
}
However it is still sorting by name. I inserted an "I'm here." log line in the comparator block & that didn't appear in my logs leading me to think the comparator isn't even being executed.
Cheers & TIA, Pedro :)
NSSortDescriptor does not accept transient property. But there is a work around.
In NSFetchedResultsController
set "sectionNameKeyPath" as your filter, and keep the NSSortDescriptor as the real column found in your core data.
So your code would be:
NSFetchedResultsController *fetcher = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:context
sectionNameKeyPath:#"sortname"
cacheName:nil];
You probably want to use a sort descriptor with a block or selector (function) e.g.
+[NSSortDescriptor sortDescriptorWithKey:ascending:comparator:]
With a block or selector, you can create as complex and customized a sort as you need.
You can also use multiple sorts in the same fetch request just remember that the sorts are evaluated in array sequence.

iOS/Objective-C: Attempting to Scan a string for substrings which will be assigned to multiple NSStrings

I'm attempting to complete the Stanford iPhone Programming (FA10) assignement "Flickr Fetcher" -- so far things are going well, however I have come to an impasse:
I have successfully extracted the location of the "Top 100" pictures, which are formated in a string as "Country, State, City". I would like to create two NSStrings -- one being the country, the other string being the State and City. From where I can then do
cell.textLabel.text = countryString;
cell.detailTextLabel.text = stateCityString;
in my table view datasource methods.
From research on stackoverflow and the Apple Documentaion, NSScanner seems to be my best bet -- here is what I have so far...
- (void)viewDidLoad {
//Get the top 100 photos from Flickr
self.topPlacesArray = [FlickrFetcher topPlaces];
NSString *mainLabelString = [[NSString alloc] init];
NSString *stringFromArray = [[NSString alloc] init];
//This retrieves the string of the location of each photo
stringFromArray = [topPlacesArray valueForKey:#"_content"];
NSScanner *theScanner = [NSScanner scannerWithString:stringFromArray];
NSCharacterSet *commaSet = [[NSCharacterSet alloc] init];
commaSet = [NSCharacterSet characterSetWithCharactersInString:#","];
while ([theScanner isAtEnd] == NO) {
if ([theScanner scanUpToCharactersFromSet:commaSet intoString:&stringFromArray]) {
NSLog(#"%#",stringFromArray);
}
}
I'm just trying to see if the string properly substrings itself -- however I am getting a "SIGBART" at the beggining of the while loop, the error is this:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI length]: unrecognized selector sent to instance 0x8939eb0'
From all the documentation I have seen on NSScanner, it seems I have it set up properly, however, no matter what changes I do, it seems unable to even begin the loop.
What do I have to do to set up NSScanner properly, to avoid the "SIGABRT"? (for the record, i'm assuming "SIGABRT" is a segfault?). Thank you all for your time, you all are the best!
(Btw: I know this is not fully implemented yet for both country and state-city, i just want to get used to NSScanner, I will implement the rest once I get NSScanner under control)
EDIT 1: SosBorn! You are incredible! Thank you so much! So I have implemented this for my viewDidLoad:
- (void)viewDidLoad
{
self.topPlacesArray = [FlickrFetcher topPlaces];
NSArray *ArrayOfStrings = [[NSArray alloc] init];
NSArray *placeElements = [[NSArray alloc] init];
NSString *country = [[NSString alloc] init];
NSString *city = [[NSString alloc] init];
NSString *state = [[NSString alloc] init];
ArrayOfStrings = [topPlacesArray valueForKey:#"_content"];
for (NSString *place in ArrayOfStrings) {
placeElements = [place componentsSeparatedByString:#", "];
if ([placeElements count] == 3 && [placeElements objectAtIndex:0] != nil) {
city = [placeElements objectAtIndex:0];
[self.cityArray addObject:city];
state = [placeElements objectAtIndex:1];
[self.stateArray addObject:state];
country = [placeElements objectAtIndex:2];
[self.countryArray addObject:country];
NSLog(#"%#, %#, %#", city, state, country);
}
else {
NSLog(#"Did this work?");
}
}
[ArrayOfStrings release];
[placeElements release];
[country release];
[city release];
[state release];
[super viewDidLoad];
}
This worked like a complete charm BUT i'm having some bad access going on in the Delegate when trying to access self.window.rootViewController = self.viewController -- this doesn't make any-sense (i actually have a completely empty table, etc...) -- so i'm thinking I played with bad memory management with my substring-ing and now it gets in trouble with this delegate call.
Chuck, I was very interested in your comment as I was taught that the proper way to make variables is to call [myclass alloc] init]; and then release when you are done -- as I have. Of course my objective-C greenness is showing a bit... blush.
You all and this incredible community are such an asset to us Students -- thank you for all your time and dedication. The only path to progress is a path of cooperation!
EDIT 2: Ok -- now it's totally fixed with no terrible leaking problems. Chuck you were right! I had the pricniples of alloc init completely mixed up in my head -- here was my final solution:
NSMutableArray *array1 = [[NSMutableArray alloc] init];
NSMutableArray *array2 = [[NSMutableArray alloc] init];
NSMutableArray *array3 = [[NSMutableArray alloc] init];
self.cityArray = array1;
self.countryArray = array2;
self.stateArray = array3;
[array1 release];
[array2 release];
[array3 release];
NSArray *ArrayOfStrings = [topPlacesArray valueForKey:#"_content"];
NSArray *topPlaces = [NSArray arrayWithArray:ArrayOfStrings];
NSArray *topPlacesSorted = [topPlaces sortedArrayUsingSelector:#selector(compare:)];
ArrayOfStrings = topPlacesSorted;
for (NSString *place in ArrayOfStrings) {
NSArray *placeElements = [place componentsSeparatedByString:#", "];
if ([placeElements count] == 3 && [placeElements objectAtIndex:0] != nil) {
NSString *city = [placeElements objectAtIndex:0];
[self.cityArray addObject:city];
NSString *state = [placeElements objectAtIndex:1];
[self.stateArray addObject:state];
NSString *country = [placeElements objectAtIndex:2];
NSString *stateAndCountry = [NSString stringWithFormat:#"%#, %#", state, country];
[self.countryArray addObject:stateAndCountry];
NSLog(#"%#, %#, %#", city, state, country);
}
else {
NSLog(#"Nil Request");
}
Thank you again SosBorn, i was feeling like I had forgotten the basics of CS ಠ_ಠ.
The only thing that really bothers me is why do we have to initialize instance NSMutableArrays that way -- i found this was the only way to get them to actually work.
Not totally sure why it is crashing, but I think another approach to this would serve you better. You have a topPlacesArray, why not iterate through the array and process each array entry seperately? I am making some assumptions about the topPlacesArray, but it would look something like this:
for (NSString *place in topPlacesArray)
{
//Place is probably in this format: "Country, State, City"
NSArray *placeElements = [place componentsSeperatedByString:#","];
//This should give you an array with three elements. Country State and city.
NSString *country = [placeElements objectAtIndex:0];
NSString *cityState = [NSString stringWithFormat:#"%#, %#", country, cityState];
//Now you have your strings that you need. Do whatever you need to do with them.
//Add them to an array or set the value of a text label, etc.
}
Didn't take the time to handle memory management but you get the idea.

iPad app crashing with no crash log while reading from file

The basic structure of my program has the user select an item from a UITableView, which corresponds to a stored text file. The file is then read into an array and a dictionary, where the array has the keys (I know I can just get the keys from the dictionary itself, this isn't my question).
The view is then changed to a UISplitView where the master view has the keys, and the detail view has the items in the dictionary attributed to that key. In this case, it's a series of "Yes/No" questions that the user selects the answer to.
My problem is this: When I click on a cell in the UITableView (first screen), it works fine, the data is read in perfectly, and so on. When I go back to the UITableView and click on the same cell again, the program crashes. Here is the read-in-from-file method:
-(NSArray *)readFromFile:(NSString *)filePath{
// NSLog(#"Path was: %#", filePath);
NSString *file = [[NSString alloc] initWithContentsOfFile:filePath];
// NSLog(#"File was: %#", file);
NSScanner *fileScanner = [[NSScanner alloc] initWithString:file];
NSString *held;
NSString *key;
NSMutableArray *detailStrings;
NSMutableArray *keys = [[NSMutableArray alloc] init];
NSMutableDictionary *details = [[NSMutableDictionary alloc] init];
/**
This is where the fun stuff happens!
**/
while(![fileScanner isAtEnd]){
//Scan the string into held
[fileScanner scanUpToString:#"\r" intoString:&held];
NSLog(#"Inside the while loop");
// If it is a character, it's one of the Key points, so we do the management necessary
if ([[NSCharacterSet lowercaseLetterCharacterSet] characterIsMember:[[held lowercaseString] characterAtIndex: 0]]){
NSArray *checkers = [[NSArray alloc] initWithArray:[held componentsSeparatedByString:#"\t"]];
NSLog(#"Word at index 2: %#", [checkers objectAtIndex:2]);
if(detailStrings != nil){
[details setObject:detailStrings forKey:key];
[detailStrings release];
}
NSLog(#"After if statement");
key = [checkers objectAtIndex:2];
[keys addObject:(NSString *) key];
detailStrings = [[NSMutableArray alloc] init];
}
else if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:[[held lowercaseString] characterAtIndex: 0]]){
NSArray *checkers = [[NSArray alloc] initWithArray:[held componentsSeparatedByString:#"\t"]];
NSLog(#"Word at index 1: %#", [checkers objectAtIndex:1]);
[detailStrings addObject:[checkers objectAtIndex:1]];
}
}
NSLog(#"File has been read in");
[details setObject:detailStrings forKey:key];
NSArray *contents = [[NSArray alloc] initWithObjects:(NSMutableArray *) keys, (NSMutableDictionary *) details, nil];
[detailStrings release];
return contents;
}
I've determined that the program crashes inside the
if(detailStrings != nil)
statement. I figure this is because I'm missing some memory management that I am supposed to be doing, but don't have the knowledge of where it's going wrong. Any ideas as to the problem, or why it is crashing without giving me a log?
detailStrings is not initialized when you enter the while loop. When you declare NSMutableArray *detailStrings; inside a method, detailStrings is not automatically set to nil. So when you do
if ( detailStrings != nil ) { .. }
it enters the if statement and since it is not initialized, it will crash when you access detailStrings.
Another thing is that detailStrings won't be initialized if it enters the else part of the loop first. That will cause a crash too. So based on your requirement, either do
NSMutableArray *detailStrings = nil;
or initialize it before you enter the while loop.
Deepak said truth. You should initialize detailStrings with nil first.
But there is second possible issue:
I recommend also to set nil after release, because in the next loop you may test nonexistent part of memory with nil.
if(detailStrings != nil){
[details setObject:detailStrings forKey:key];
[detailStrings release];
detailStrings = nil;
}
And the third possible issue: depending from incoming data you may go to the second part of IF statement first time and try to addObject into non-initialized array.
The fourth (hope last): you have memory leak with "checkers" arrays
Here's what I'm seeing:
//read in the file
NSString *file = [[NSString alloc] initWithContentsOfFile:filePath];
//create the scanner
NSScanner *fileScanner = [[NSScanner alloc] initWithString:file];
//declare some uninitialized stuff
NSString *held;
NSString *key;
NSMutableArray *detailStrings;
//initialize some stuff
NSMutableArray *keys = [[NSMutableArray alloc] init];
NSMutableDictionary *details = [[NSMutableDictionary alloc] init];
//begin loop
while(![fileScanner isAtEnd]){
//scan up to a newline
[fileScanner scanUpToString:#"\r" intoString:&held];
//see if you scanned a lowercase string
if ([[NSCharacterSet lowercaseLetterCharacterSet] characterIsMember:[[held lowercaseString] characterAtIndex: 0]]){
//make an array
NSArray *checkers = [[NSArray alloc] initWithArray:[held componentsSeparatedByString:#"\t"]];
//do a check... against an uninitialized value
if(detailStrings != nil){
//set a potentially uninitialized value into an array with an uninitialized key
[details setObject:detailStrings forKey:key];
At this point, you're pretty much hosed.
The fix:
properly initialize your variables
run the static analyzer
read the memory management programming guide