How to sort GKTurnBasedMatch by most recently active? - iphone

I am creating a simple word game with a menu screen in which I am displaying all of the user's active matches. I would like to sort this array of matches in order from most recently to least recently active, but the only timestamp property associated with players taking turns is a property of GKTurnBasedParticipant...GKTurnBasedMatch has no useful sorting property.
GKTurnBasedMatch has an array of GKTurnBasedParticipant objects as a property, so I would certainly be able to come up with some sort of solution, but I can't think of anything that wouldn't be really messy and inefficient. Is there any way something simple like NSPredicate could be used in a case like this to drill down into each array of participants, look at the latest timestamp and sort all the matches in one go?

I don't have an NSPredicate-based solution, or probably anything as elegant as you had hoped, but I ran into the same issue and wrote my own solution and it wasn't actually that bad.
My solution is for a game that can only have two participants, so modify accordingly, but here is the code I ended up using:
[myGamesArray sortUsingComparator:^NSComparisonResult(CHGame *game1,
CHGame *game2) {
if (YES == [game1 localPlayersTurn] && NO == [game2 localPlayersTurn]) {
return NSOrderedAscending;
} else if (NO == [game1 localPlayersTurn] && YES == [game2 localPlayersTurn]) {
return NSOrderedDescending;
}
NSDate *lm1 = [game1.match lastMove];
NSDate *lm2 = [game2.match lastMove];
if (lm1 != nil && lm2 != nil) {
return [lm1 compare:lm2];
}
return NSOrderedSame;
}];
where CHGame is a custom class I built for my games (which have a GKTurnBasedMatch match property), and the instance method localPlayersTurn returns a BOOL indicating whether or not it is the local participant's turn or not.
And then I wrote a lastMove method in a category on GKTurnBasedMatch:
- (NSDate *)lastMove {
GKTurnBasedParticipant *localParticipant, *otherParticipant;
NSDate *lastMove;
for (GKTurnBasedParticipant *participant in self.participants) {
if (YES == [participant.playerID isEqualToString:[GKLocalPlayer localPlayer].playerID]) {
localParticipant = participant;
} else {
otherParticipant = participant;
}
}
if (localParticipant == self.currentParticipant) {
lastMove = otherParticipant.lastTurnDate;
} else {
lastMove = localParticipant.lastTurnDate;
}
return lastMove;
}
Again, this only works for two total participants, but would be easy to modify for any number of them.
Hope this helps even though it's not exactly what you asked for.

Sort turn-based matches by last turn of current participant
[GKTurnBasedMatch loadMatchesWithCompletionHandler:^(NSArray *matches, NSError *error)
{
NSString *descriptorKey = #"currentParticipant.lastTurnDate";
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:descriptorKey
ascending:NO];
NSArray *sortedMatches = [matches sortedArrayUsingDescriptors:#[sortDescriptor]];
}];
Sort turn-based matches by date created
[GKTurnBasedMatch loadMatchesWithCompletionHandler:^(NSArray *matches, NSError *error)
{
NSString *descriptorKey = #"creationDate";
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:descriptorKey
ascending:NO];
NSArray *sortedMatches = [matches sortedArrayUsingDescriptors:#[sortDescriptor]];
}];

Related

Retrieve scores of two friends above GKLocalPlayer

I want to show a very small leaderboard snippet on my games front page, basically showing your score and the scores of two friends around you (above and below you're score). I have read through the apple documentation and cannot see a way to do this unless I specify all friends and specify a huge range to ensure I get all the friends, which I can then filter. It seems inefficient to retrieve this list especially as the user may be on mobile. How can I achieve what I want without downloading the entire list of friends and then filtering?
Here is what I currently have (without filtering)
GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] init];
if (leaderboardRequest != nil)
{
leaderboardRequest.playerScope = GKLeaderboardPlayerScopeFriendsOnly;
leaderboardRequest.timeScope = GKLeaderboardTimeScopeAllTime;
leaderboardRequest.category = #"HighScore";
leaderboardRequest.range = NSMakeRange(1,100);
[leaderboardRequest loadScoresWithCompletionHandler: ^(NSArray *scores, NSError *error) {
if (error != nil)
{
// Handle the error.
}
if (scores != nil)
{
GKScore* myScore = leaderboardRequest.localPlayerScore;
NSLog(#"Me: %#: %d",myScore.playerID, (int)myScore.value);
// Process the score information - here I would filter
for (GKScore* score in scores)
{
NSLog(#"%#: %d",score.playerID, (int)score.value);
}
}
}];
}
The following code may help you.
From Game Center Programming Guide:
GKLeaderboard range: You can pick scores within a specific range. For example, the range [1,10] returns the best ten scores found by the query.
GKScore rank: The position of the score in the results of a leaderboard search.
ps. I couldn't test it.
Best regards.
GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] init];
if (leaderboardRequest != nil)
{
GKScore* myScore = leaderboardRequest.localPlayerScore;
leaderboardRequest.playerScope = GKLeaderboardPlayerScopeFriendsOnly;
leaderboardRequest.timeScope = GKLeaderboardTimeScopeAllTime;
leaderboardRequest.category = #"HighScore";
leaderboardRequest.range = NSMakeRange(myScore.rank-1, myScore.rank+1);
[leaderboardRequest loadScoresWithCompletionHandler: ^(NSArray *scores, NSError *error) {
if (error != nil)
{
// Handle the error.
}
else
{
// ...
}
}];
}

App becomes unresponsive on NSFetchRequests

I have the above CoreData Model in my first iPad app. I'm building a filtering system in a TableViewController as shown below. The problem is that whenever I make a UI change, toggle a switch of tap a button, My UI becomes non-responsive for a second or two. I run a really long function that recreates the fetch request for the photos, then runs more count fetches to determine whether the control should be enabled. I just don't know how I can break this apart in a meaningful way that would prevent the hang. Even If I need to add a spinning view for a second or so, I'm happy with that. Just want to get rid of the lag.
As I mentioned, this is my first attempt at iOS development so I would appreciate any suggestions...
-(void) refilterPhotos {
/*
* First section builds the NSCompoundPredicate to use for searching my CoreData Photo objects.
Second section runs queries so 0 result controls can be disabled.
*/
subpredicates = [[NSMutableArray alloc] init];
NSPredicate *isNewPredicate;
if(newSwitch.on) {
isNewPredicate = [NSPredicate predicateWithFormat:#"is_new == 1"];
} else {
isNewPredicate = [NSPredicate predicateWithFormat:#"is_new == 0"];
}
[subpredicates addObject:isNewPredicate];
//Photo Types
PhotoType *photoType;
NSPredicate *photoTypePredicate;
for (UISwitch *photoSwitch in photoSwitches) {
PhotoType * type = (PhotoType *) photoSwitch.property;
if([type.selected boolValue] == YES) {
NSLog(#"photo_type.label == %#", type.label);
photoType = type;
photoTypePredicate = [NSPredicate predicateWithFormat:#"photo_type.label == %#", type.label];
break;
}
}
//Feed Types
FeedType *feedType;
NSPredicate *feedTypePredicate;
for (UISwitch *feedSwitch in feedSwitches) {
FeedType * type = (FeedType *) feedSwitch.property;
if([type.selected boolValue] == YES) {
NSLog(#"feed_type.label == %#", type.label);
feedType = type;
feedTypePredicate = [NSPredicate predicateWithFormat:#"feed_type.label == %#", type.label];
break;
}
}
//Markets
NSArray *filteredMarkets = [model.availableMarkets filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"selected == 1"]];
for (Market *market in filteredMarkets) {
[subpredicates addObject:[NSPredicate predicateWithFormat:#"ANY markets.name == %#", market.name]];
}
//Tags
NSArray *filteredTags = [model.availableTags filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"selected == 1"]];
for (Tag *tag in filteredTags) {
NSLog(#"ANY tags.name == %#",tag.name);
[subpredicates addObject:[NSPredicate predicateWithFormat:#"ANY tags.name == %#", tag.name]];
}
if(photoTypePredicate)
[subpredicates addObject:photoTypePredicate];
if(feedTypePredicate)
[subpredicates addObject:feedTypePredicate];
NSPredicate *finished = [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];//Your final predicate
model.availablePhotos = [model fetchPhotoswithPredicate:finished];
[[self parentViewController] setTitle:[NSString stringWithFormat:#"%d items",[model.availablePhotos count]]];
NSLog(#"FILTERED PHOTOS:::: %d", [model.availablePhotos count]);
[gridVC reloadGrid];
/**
* Filtering Section Here, I'm running count requests for each grouping of controls to ensure if they're selected, results will be returned.
* If zero results, I'll disable that control. For the switch-based controls, I need to removed them before running my fetches since there can only be
* one switch value per photo.
*/
//Have to remove the existing type predicate since they're exlcusive values
[subpredicates removeObject:isNewPredicate];
//New Toggle
NSPredicate *newRemainderPredicate = [NSPredicate predicateWithFormat:#"is_new == %d",newSwitch.on?0:1];
[subpredicates addObject:newRemainderPredicate];
if([model countPhotoswithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]]<1) {
[newSwitch setEnabled:NO];
} else {
[newSwitch setEnabled:YES];
}
[subpredicates removeObject:newRemainderPredicate];
[subpredicates addObject:isNewPredicate];
[subpredicates removeObject:photoTypePredicate];
//Photo Type Toggles
NSArray *remainderPhotoTypes = [photoSwitches filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"on == NO"]];
for( UISwitch*control in remainderPhotoTypes) {
PhotoType *remainderPhotoType = (PhotoType*)control.property;
[subpredicates addObject:[NSPredicate predicateWithFormat:#"photo_type == %#", remainderPhotoType]];
if([model countPhotoswithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]]<1) {
//NSLog(#"PHOTOTYPE OFF %#", remainderPhotoType.label);
control.enabled = NO;
} else {
//NSLog(#"PHOTOTYPE ON %# count = %d", remainderPhotoType.label, [[model fetchPhotoswithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]] count]);
control.enabled = YES;
}
remainderPhotoType.enabled = [NSNumber numberWithBool:control.enabled];
[subpredicates removeObject:[NSPredicate predicateWithFormat:#"photo_type == %#", remainderPhotoType]];
}
if(photoTypePredicate)
[subpredicates addObject:photoTypePredicate];
[subpredicates removeObject:feedTypePredicate];
//Feed Type Toggles
NSArray *remainderFeedTypes = [feedSwitches filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"on == NO"]];
for( UISwitch*control in remainderFeedTypes) {
PhotoType *remainderFeedType = (PhotoType*)control.property;
[subpredicates addObject:[NSPredicate predicateWithFormat:#"feed_type == %#", remainderFeedType]];
if([model countPhotoswithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]]<1) {
control.enabled = NO;
} else {
control.enabled = YES;
}
remainderFeedType.enabled = [NSNumber numberWithBool:control.enabled];
[subpredicates removeObject:[NSPredicate predicateWithFormat:#"feed_type == %#", remainderFeedType]];
}
if(feedTypePredicate)
[subpredicates addObject:feedTypePredicate];
NSArray *remainderMarkets = [[model availableMarkets] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"selected == 0"]];
//Markets..many-to-many so I don't remove the existing predicate
for( Market *remainderMarket in remainderMarkets) {
[subpredicates addObject:[NSPredicate predicateWithFormat:#"ANY markets == %#", remainderMarket]];
NSInteger countForTag = [model countPhotoswithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]];
if(countForTag<1) {
remainderMarket.enabled = [NSNumber numberWithInt:0];
} else {
remainderMarket.enabled = [NSNumber numberWithInt:1];
}
[subpredicates removeObject:[NSPredicate predicateWithFormat:#"ANY markets == %#", remainderMarket]];
}
NSArray *remainderTags = [[model availableTags] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"selected == 0"]];
//TAGS..many-to-many so I don't remove the existing predicate
int tagCounter = 0;
for( Tag *remainderTag in remainderTags) {
[subpredicates addObject:[NSPredicate predicateWithFormat:#"ANY tags == %#", remainderTag]];
NSInteger countForTag = [model countPhotoswithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]];
if(countForTag<1) {
NSLog(#"TAG OFF %#", remainderTag.name);
remainderTag.enabled = 0;
} else {
NSLog(#"TAG ON %# count = %d", remainderTag.name, countForTag);
remainderTag.enabled = [NSNumber numberWithInt:1];
}
[subpredicates removeObject:[NSPredicate predicateWithFormat:#"ANY tags.name == %#", remainderTag.name]];
tagCounter ++;
}
//Update the controls with this new data
[self.tableView reloadData];
}
OK, there are several things to consider here.
First, I would consider creating indexes for the main search fields. Without an index, each search is linear, because it has to check the value of each record. An index will result in much faster searching times.
Second, I'd be very careful about ordering in a compound predicate. It will filter them based on order. Thus, you want to make you fastest, most filtering predicates first. Trim the possible solution space a quickly as possible.
You can gain a lot by indexing the attributes you use in the first 1-3 predicates. I note at the bottom, when you query for counts, you are still using the same compound predicate. Do you really want that? Also, in this code
//Have to remove the existing type predicate since they're exlcusive values
[subpredicates removeObject:isNewPredicate];
//New Toggle
NSPredicate *newRemainderPredicate = [NSPredicate predicateWithFormat:#"is_new == %d",newSwitch.on?0:1];
[subpredicates addObject:newRemainderPredicate];
You are removing the is_new check from the front, and placing it at the rear. If you are just checking this one predicate to toggle that switch, and you only care to see if there are 0 or more, why even use the entire compound predicate? Is the "toggle" going to be on/off relative to all the other fields?
If you continue with this, remember, it is going to do all those other predicates first (and some are references). Try to keep them in a good order to filter as much as possible, as quickly as you can.
Third, using references is convenient, but expensive. You can possible get better performance by querying those separately, and then using the compound predicate to filter the in-memory objects.
Fourth, you should be executing all these queries in a separate thread. That is very easy to do, but the exact method depends on your current ManagedObjecttContext arrangement. Do you have a single MOC, a parent/child relationship, a UIManagedDocument? Basically, you can create a separate MOC, and call performBlock to execute the fetches. In fact, you can fire all those fetches off asynchronously at the same time with multiple MOCs.
Then, you can just call into the main thread when they are done.
Finally, you may want to consider denormalizing your database. It will cause you to use more space, but fetches will be much faster. Specifically, the relationship fields... you could put the photo/feed labels in with the Photo itself. That way, when searching, you don't have to do the extra join to get those records.
So, it's not a simple answer, but implement each of these, and see if your performance does not improve considerably (not to mention your UI responsiveness).

Check two NSArrays for containing each other's objects (NSManagedObject)

I'm stuck at following problem for quite some time now:
I've got two NSArrays, both containing NSManagedObject subclass-objects.
They're fed by different sources but the objects in them still have the same properties/values.
What I want to do now is check if array A contains objects from array B and vice versa.
Unfortunately NSArray's containsObject-method doesn't seem to work here.
I think it uses id-testing for the equality check on each object, doesn't it?
So, does anybody have a clue, what to try?
I even tried to encapsulate my objects in NSSets, using member: as my comparison-method but this didn't work out as well, especially because "you must not override" isEqual etc. for NSManagedObject subclasses.
Here's a code snippet:
//manufacturers is an array, parsed out of some xml here...
for(Manufacturer *manu in [fetchedResultsController fetchedObjects])
{
if(![manufacturers containsObject:manu])
{
NSLog(#"Deleting %#", manu.name);
[self.mContext deleteObject:manu];
}
}
for(Manufacturer *manu in manufacturers)
{
if(![[fetchedResultsController fetchedObjects] containsObject:manu])
{
NSLog(#"Adding %#", manu.name);
[newArray addObject:manu];
}
}
Thanks in advance for any hint ;)
I'm not sure if this works, but you could try to match the dictionaries you get with dictionaryWithValuesForKeys:.
Something like this:
NSArray *keysToCompare = [NSArray arrayWithObjects:#"FooAttribute", #"BarAttribute", nil];
// create an array with the dictionary representation of the managedObject
NSMutableArray *fetchedObjectsDictionaries = [NSMutableArray arrayWithCapacity:[[fetchedResultsController fetchedObjects] count]];
for (NSManagedObject *object in [fetchedResultsController fetchedObjects]) {
NSDictionary *dictionaryRepresentation = [object dictionaryWithValuesForKeys:keysToCompare];
[fetchedObjectsDictionaries addObject:dictionaryRepresentation];
}
// another array with dictionaries for managedObjects
NSMutableArray *manufacturersDictionaries = [NSMutableArray arrayWithCapacity:[manufacturers count]];
for (NSManagedObject *object in manufacturers) {
NSDictionary *dictionaryRepresentation = [object dictionaryWithValuesForKeys:keysToCompare];
[manufacturersDictionaries addObject:dictionaryRepresentation];
}
// compare those dictionaries
for (NSInteger i = 0; i < [fetchedObjectsDictionaries count]; i++) {
NSDictionary *dictionary = [fetchedObjectsDictionaries objectAtIndex:i];
if (![manufacturersDictionaries containsObject:dictionary]) {
// get the corresponding managedObject
NSManagedObject *object = [[fetchedResultsController fetchedObjects] objectAtIndex:i];
[newArray addObject:object];
}
}
if that won't work you can write your own isEqualToManufacturer: method and enumerate trough the arrays manually.
There would be 3 types of equality you can check for: same memory address, managed object id equality, and value equality. Your current code already checks to see if the objects share the same memory address and this is most likely not what you are interested in. This leaves two possible options. Using the managed object id equality method you can check if the manufacturers point to the same row in the database. Using the value equality you can check if two manufacturers are equal based on the shared values. Below is a way to check for NSManagedObjectID equality.
for(Manufacturer *manu in [fetchedResultsController fetchedObjects])
{
id databaseIDTest = ^(Manufacturer * checkManu, NSUInteger idx, BOOL *stop){
return [[checkManu objectID] isEqual:[manu objectID]];
};
if([manufacturers indexOfObjectPassingTest:databaseIDTest] == NSIndexNotFound)
{
NSLog(#"Deleting %#", manu.name);
[self.mContext deleteObject:manu];
}
}
for(Manufacturer *manu in manufacturers)
{
id databaseIDTest = ^(Manufacturer * checkManu, NSUInteger idx, BOOL *stop){
return [[checkManu objectID] isEqual:[manu objectID]];
};
NSArray * fetchedObjects = [fetchedResultsController fetchedObjects];
if([fetchedObjects indexOfObjectPassingTest:databaseIDTest] == NSIndexNotFound)
{
NSLog(#"Adding %#", manu.name);
[newArray addObject:manu];
}
}
You need to override -isEqual: since that's what -[NSArray containsObject:] calls into:
- (BOOL)isEqual:(id)other;
{
if (![other isKindOfClass:[Manufacturer class]]) {
return NO;
}
Manufacturer *otherManufacturer = other;
return ([self.name isEqual:otherManufacturer.name] &&
...
);
}
Checking for containment inside an NSSet is cheaper (and may make sense if you run into performance problems). It only works if you have a relatively decent -hash implementation, but it's easy to implement like this:
- (NSUInteger)hash;
{
return [self.name hash] + [self.foo hash] + ...;
}
Don't go trough too much trouble with the hash, just use 2 - 3 values that are most likely to uniquely identify the object.

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];

Sort an NSArray in Descending Order

I have an NSArray of NSNumber objects that I have successfully sorted in ascending order using the following:
[myArray sortedArrayUsingSelector:#selector(compare:)]
However, I need to sort this in descending order. I take it that compare: only sorts in ascending order. While I can go about reversing the NSArray, I am curious as to whether or not there is a more simpler or more effective way of going about doing this.
EDIT: I found this question which provides a simple way to reverse iterate an NSArray:
for (id someObject in [myArray reverseObjectEnumerator])
This works fine, I guess it's a nice simple solution, but I'm curious as to whether or not there is a way to specify sorting in descending order.
Use a sort descriptor
NSSortDescriptor* sortOrder = [NSSortDescriptor sortDescriptorWithKey: #"self"
ascending: NO];
return [myArray sortedArrayUsingDescriptors: [NSArray arrayWithObject: sortOrder]];
Another way, imo, is also nice: write another reverseCompare with category:
#implementation NSNumber (Utility)
- (NSComparisonResult)reverseCompare:(NSNumber *)aNumber {
return [aNumber compare:self];
}
The good thing is that you can reuse everywhere and don't have to write the loop with reverse iterate. The bad thing is that you have to write more code:)
use sortarrayusingcomparator method like that
NSArray *sortedArray = [array sortedArrayUsingComparator:
^NSComparisonResult(id obj1, id obj2){
return [obj2 compare:obj1];
}];
by reversing the order of objects you will obtain a descending order
There are a few ways:
Write a comparison function and pass it to sortUsingFunction:context:.
Use sortUsingComparator:, which takes a block. (Only available in Mac OS X 10.6 and later and iOS 4.0 and later.)
Use sortUsingDescriptors:, and pass an array of at least one sort descriptor whose ascending property is false.
In the first two cases, your comparison should simply return the negation of what the comparator you normally use (e.g., compare:) returned. The last one involves less custom code, so it's what I'd use.
I have a mutableArray and want to sort in descending order with "number" key. "number" key is a string like "12345", "12346". This way that I had tried and look very well. Hope help you!
NSComparisonResult result;
NSArray *arrTem = [multableArr sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
if([obj1 valueForKey:#"number"] > [obj2 valueForKey:#"number"])
result = NSOrderedDescending;
result = NSOrderedSame;
}];
return [[NSMutableArray alloc]initWithArray:arrTem];
For Example Data is Like this and we want to sort NSArray based on sId key.
<__NSArrayM 0x7ffc725af1d0>(
{
sId = 3;
vName = ABC;
},
{
sId = 10;
vName = DEF;
},
{
sId = 9;
vName = GHI;
},
{
sId = 7;
vName = JKL;
},
{
sId = 1;
vName = MNO;
}
)
Solution is as Below
NSArray *sortedArray = [arrOptions sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
if ([[obj1 valueForKey:#"sId"] integerValue] > [[obj2 valueForKey:#"sId"] integerValue]) {
return (NSComparisonResult)NSOrderedDescending;
}
if ([[obj1 valueForKey:#"sId"] integerValue] < [[obj2 valueForKey:#"sId"] integerValue]) {
return (NSComparisonResult)NSOrderedAscending;
}
return (NSComparisonResult)NSOrderedSame;
}];
NSLog(#"Sorted Service Array is ::%#",sortedArray);
comare selector has to return NSComparisonResult. It's actually your function, so you can write another function, that returns the opposite result to sort in descending order.
The Dr. Touch website has a nice implementation of a category on NSArray to create a self-sorting array. This could be modified to keep the sort in whatever order was appropriate.
If you need based on ABCD in ascending then use following code
NSSortDescriptor *descriptor=[[NSSortDescriptor alloc] initWithKey:#"self" ascending:YES];
NSArray *descriptors=[NSArray arrayWithObject: descriptor];
NSArray *reverseOrder=[yourArray sortedArrayUsingDescriptors:descriptors];
we can do it simply by using sortUsingSelector. The code as follows;
NSMutableArray *arr=[[NSMutableArray alloc]initWithObjects: ..objects.. ];
//Can also use NSArray.
[arr sortUsingSelector:#selector(compare:options:)];
NSString *temp;
int i,j;
i=0;
j=[arr count];
for(i=0;i<([arr count]-1);i++)
{
temp=[arr objectAtIndex:i];
[arr replaceObjectAtIndex:i withObject:[arr objectAtIndex:j]];
[arr replaceObjectAtIndex:j withObject:temp];
j--;
}
NSLog(#"New array is:%#",arr);
You may think that WHAT A STUPID ANSWER it is :D :D Actually, am not teasing anyone. But in simple logic, we can sort array in descending order by this way. No need of using any type of descriptors or any other complicated stuffs. And it will be easier for entry level programmers.
Use the following code to sort Array on the basis of key on which you want to sort tge array (e.g name , code, address,emp_id etc...)
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"Enter Sort Type" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
return [array sortedArrayUsingDescriptors:sortDescriptors];