3

I have a mac app with a scrollview that displays a list of items. The data for the items is provided from Core Data. I need to provide a 'shuffle' button that with randomize the order of the items within the scroll view.

TechZen
  • 64,370
  • 15
  • 118
  • 145
Zakman411
  • 1,764
  • 3
  • 22
  • 45
  • Did you try this? http://www.google.com/search?q=nsarray%20shuffle – Dave DeLong Mar 30 '11 at 23:12
  • possible duplicate of [canonical way to randomize an NSArray in Objective C](http://stackoverflow.com/questions/791232/canonical-way-to-randomize-an-nsarray-in-objective-c) – Dave DeLong Mar 30 '11 at 23:12
  • 1
    @Dave: This is a mac app, and also not an array - its core data – Zakman411 Mar 31 '11 at 02:33
  • CoreData is the same on the Mac as the iPhone, and regardless, the only way that CoreData gives you information is an array of objects. If you want to display those objects in a random order, you'll have to shuffle the array. CoreData does not have a "sort by random" option. – Dave DeLong Mar 31 '11 at 03:15

1 Answers1

3

If you are using bindings, you have only two choices: (1), bind to a custom object which has a key that returns the sorted array or (2) use a sort descriptor that randomizes instead of ordering.

The second option is easier in some circumstance. To produce a randomizing sort descriptor you can (1) create a NSSortDescriptor subclass and override compareObject:toObject:

- (NSComparisonResult)compareObject:(id)object1 toObject:(id)object2{
  NSUInteger ran=(arc4random() % 3);
  switch (ran) {
    case 0:
      return NSOrderedSame;
      break;
    case 1:
      return NSOrderedDescending;
    default:
      return NSOrderedAscending;
      break;
  }  
}

or (2) provide a selector or block that provides a random ordering in the same way:

  NSSortDescriptor *rs=[NSSortDescriptor sortDescriptorWithKey:@"intValue" 
                                                    ascending:YES
                                                   comparator:^(id obj1, id obj2){
                                                     NSUInteger ran=(arc4random() % 3);
                                                     switch (ran) {
                                                       case 0:
                                                         return NSOrderedSame;
                                                         break;
                                                       case 1:
                                                         return NSOrderedDescending;
                                                       default:
                                                         return NSOrderedAscending;
                                                         break;
                                                     }
                                                   }];

The disadvantage of (2) is that you must provide a key that the object being sorted to understands e.g. in the above, I sorted an array of NSNumber objects so I have to provide a key intValue that NSNumber responds to. The subclass method does not really care about keys.

Using the sorts, however, returns only a weakly randomized array because the randomization is just a messed up sort i.e. most of the elements do not move very far in the array. To create a more randomized effect, stack the randomizing sorts into an array so that they make multiple passes over the array e.g.:

[ aMutableArray sortUsingDescriptors:[NSArray arrayWithObjects:rs,rs,rs, nil]];

If you use bindings, just use the randomizing sort descriptors where you use a normal sort descriptor and you should get something that is usefully randomized.

TechZen
  • 64,370
  • 15
  • 118
  • 145
  • @TechZen: Thanks for the great explanation. I think method #1 works fine for me, I'm not too concerned with the strength of the randomization. However, I'm a bit confused on how to use it. My list has a potentially infinite number of items (the user creates/deletes them) so I don't know what I'm supposed to be comparing? i.e. compareObject:(id)object1 toObject:(id)object2? – Zakman411 Mar 31 '11 at 22:50
  • You don't have to compare any objects yourself. Instead, create a subclass of NSSortDescriptor e.g. RandomSortDescriptor and then over the `compareObject:toObject:` method as above. Then just use an instance of RandomSortDescriptor anywhere you want a randomized sort. – TechZen Apr 01 '11 at 15:54
  • @TechZen: I created a new class called RandomSortDescriptor that is a subclass of NSSortDescriptor. I pasted your code into the implementation file. My only question is how I use an instance of RandomSortDescriptor in say, a scrollview? I have a button that I would like to initiate the random sort (it says "shuffle"), and created an IBAction for it.. any help here? I really appreciate your help so far – Zakman411 Apr 02 '11 at 17:57
  • @TechZen: Any help? I'm trying to fire the event on button press – Zakman411 Apr 04 '11 at 01:16
  • Are you using bindings? If so, then you should set the random sort descriptor to the scrollview upon push of the button. If not, then it might be easier to use one of the array randomizers linked to above. I presuming that you know how to create a button action. If not, then you might want to post another question asking how to do that as the explanation won't fit in a small comment. – TechZen Apr 04 '11 at 20:17
  • @TechZen: Yeah I have an IBAction for my button, linked to myAppDelegate and all. What code would fire it? – Zakman411 Apr 05 '11 at 01:14
  • If the button is linked to the IBAction method, it will fire automatically when the button is clicked. Put any code you want to activate upon the click in that button. – TechZen Apr 05 '11 at 18:48
  • @TechZen: So, assuming I want to use the first method (compareObject), I would just copy/paste that code into the IBAction? That's what I mean, I have the button ready but don't know what code to execute in the IBAction itself. I appreciate your help brotha – Zakman411 Apr 05 '11 at 21:19
  • A unique idea for creating a semi-random `NSSortDescriptor`. Problem is core data doesn't actually call `compareObject:toObject:` of the `NSSortDescriptor` subclass. It also doesn't allow for an `NSSortDescriptor` to be used without a valid key throwing `'NSInvalidArgumentException', reason: 'keypath not found in entity...` unless `initWithKey:` is used with a valid attribute property. Using method #2 `unsupported NSSortDescriptor (comparator blocks are not supported)` is thrown. Clearly core data does its own internal sorting using the `NSSortDescriptor`'s key and `ascending` boolean values. – Jeff Lockhart Jul 26 '13 at 23:56