0
votes

I'm having a problem trying to get the selection of my Collection View using Xcode 6 and trying to build an OSX app.

I followed the directions at http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/CollectionViews/Introduction/Introduction.html to create the collection view and it's working as intended but I am unable to get the selection of the item I click. I made sure that the view is selectable in IB.

I implemented the notification method mentioned in Selection Highlight in NSCollectionView and I can see when I populate the collection view that the event fires but no matter where I click in the collection view item the notification won't fire again.

As is what I think normal for collection views, I'm simply trying to get the array index of the item selected so I can show detail.

I have poured through articles on the net trying to find the solution but the vast majority of solutions are for IOS using segue's and not for OSX. The exception being the link I posted for Stack Overflow.

I've even gone as far as putting a transparent button covering my entire collection view item so I can grab a click event (which works but I still don't know which item was clicked).

My my question is: how do I get the array item of something I clicked in a collection view?

enter image description here

1
If all the bindings are correct (see answer below), this kind of behaviour can be due to one of the key objects being deallocated = ensure that all of the objects involved (e.g. the NSIndexSet instance that is being observed, and the object that owns this index set are objects that are retained (like properties). One way of checking this is to add a button to the UI, and, in the associated Action method, log the objects of interest. If one of these objects outputs nil as its description, this is possibly the problem.Paul Patterson

1 Answers

2
votes

One way of hearing about changes to the array controllers selectionIndexes is to bind this property to an NSIndexSet instance in your code somewhere, and then use the KVO design pattern to request a notification when this NSIndexSet is altered. If you set this up correctly, when the user clicks on an unselected cell in your NSCollectionView the NSIndexSet that the array controller is using to store its selection indexes is updated. Since this index set is the one you're observing, you'll get a notification telling you about the change.

In the demo-app I created to answer this question, I placed the index set in question on the AppDelegate - here's the implementation file:

// Interface ////////////////////////////////////////////////////////

@interface AppDelegate ()

// This is the content array from which the NSArrayController will derive
// it's arrangedObjects array
@property (nonatomic, strong) NSArray *collectionViewContent;

// This is the NSIndexSet that I want the NSArrayController to use
// to store its selectionIndexes.
@property (nonatomic) NSIndexSet *mySelectionIndexes;

@end

// Implementation //////////////////////////////////////////////////

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {

    // The content that will be shown in the NSCollectionView (each word
    // represents a single collection view item).
    self.collectionViewContent = @[@"The", @"rain", @"in", @"Spain", @"falls", @"..."];

    // Tell cocoa you want to know when the array controller makes changes to
    // the index set it's using to stores its selection indexes
    [self addObserver:self
           forKeyPath:@"mySelectionIndexes"
              options:0
              context:nil];
}


-(void)observeValueForKeyPath:(NSString *)keyPath
                     ofObject:(id)object
                       change:(NSDictionary *)change
                      context:(void *)context {
    NSLog(@"Collection view selection just changed to: %@", self.mySelectionIndexes);
}

//////////////////////////////////////////////////////////////////

In the Bindings Inspector for the NSArrayController you can now tell the array controller to use the AppDelegate property mySelectionIndexes to store its selection indexes:

enter image description here

If you're still experiencing problems, it's possible that you've gone wrong with the bindings somewhere else - here are all the bindings I used:

NSArrayController The array controller will get it's content from an array managed by the AppDelegate object. It will store it's selection indexes in an NSIndexSet, also managed by the AppDelegate

  1. Content Array: App Delegate.collectionViewContent
  2. Selection Indexes: App Delegate.mySelectionIndexes

NSCollectionView The collection view will get it's model data from the array controller. It will mark as selected those views that appear at the indexes stored in the array controller's selectionIndexes property:

  1. Content: Array Controller.arrangedObjects
  2. Selection Indexes: Array Controller.selectionIndexes

Onto the view that was automatically generated when I dragged the NSCollectionView onto the canvas, I've added one NSTextField, this text field has just one binding:

  1. Value: Collection View Item.representedObject (In other words, type representedObject into the Model Key Path field.

A Final Word:

It's worth pointing out that you don't have to set up this binding. To get word when one of the items in your collection view is selected or unselected by the user, create a subclass of NSCollectionViewItem and override the selected setter. This property is called automatically each time an item is selected or unselected. In your implementation, you can now make adjustments to the item's view to take account of the fact that it's status has changed. In my demo-app, my custom subclass of NSCollectionViewItem was called PPCollectionViewItem:

@implementation PPCollectionViewItem

-(void)setSelected:(BOOL)selected {
    // Call super...
    [super setSelected:selected];

    // ...now change the view associated with this item. Remember,
    // the view is one of the cells in the NSCollectionView - I
    // changed it from a standard NSView, to a subclass called
    // CollectionViewCell which keeps a flag indicating whether
    // or not it's selected (the drawRect routine is varied
    // according to this flag's value).
    [(CollectionViewCell *)self.view setDrawAsSelected:selected];
}

@end