views:

523

answers:

1

On some occasions my head just hurts from banging it against the Cocoa wall. Today is one of those days.

I have a working NSCollectionView with one minor, but critical, exception. Getting and highlighting the selected item within the collection.

I've had all this working prior to Snow Leopard, but something appears to have changed and I can't quite place my finger on it, so I took my NSCollectionView right back to a basic test and followed Apple's documentation for creating an NSCollectionView here:

http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/CollectionViews/Introduction/Introduction.html

The collection view works fine following the quick start guide. However, this guide doesn't discuss selection other than "There are such features as incorporating image views, setting objects as selectable or not selectable and changing colors if they are selected".

Using this as an example I went to the next step of binding the Array Controller to the NSCollectionView with the controller key selectionIndexes, thinking that this would bind any selection I make between the NSCollectionView and the array controller and thus firing off a KVO notification. I also set the NSCollectionView to be selectable in IB.

There appears to be no selection delegate for NSCollectionView and unlike most Cocoa UI views, there appears to be no default selected highlight.

So my problem really comes down to a related issue, but two distinct questions.

  1. How do I capture a selection of an item?
  2. How do I show a highlight of an item?

NSCollectionView's programming guides seem to be few and far between and most searches via Google appear to pull up pre-Snow Leopard implementations, or use the view in a separate XIB file.

For the latter (separate XIB file for the view), I don't see why this should be a pre-requisite otherwise I would have suspected that Apple would not have included the view in the same bundle as the collection view item.

I know this is going to be a "can't see the wood for the trees" issue - so I'm prepared for the "doh!" moment.

As usual, any and all help much appreciated.

Update 1

OK, so I figured finding the selected item(s), but have yet to figure the highlighting. For the interested on figuring the selected items (assuming you are following the Apple guide):

In the controller (in my test case the App Delegate) I added the following:

In awakeFromNib

[personArrayController addObserver:self
       forKeyPath:@"selectionIndexes" 
       options:NSKeyValueObservingOptionNew
       context:nil];

New Method

-(void)observeValueForKeyPath:(NSString *)keyPath 
                     ofObject:(id)object
                       change:(NSDictionary *)change
                      context:(void *)context
{
    if([keyPath isEqualTo:@"selectionIndexes"])
    {
        if([[personArrayController selectedObjects] count] > 0)
        {
            if ([[personArrayController selectedObjects] count] == 1)
            {
                personModel * pm = (PersonModel *) 
                       [[personArrayController selectedObjects] objectAtIndex:0];
                NSLog(@"Only 1 selected: %@", [pm name]);
            }
            else
            {
                // More than one selected - iterate if need be
            }
        }
    }

Don't forget to dealloc for non-GC

-(void)dealloc
{
    [personArrayController removeObserver:self 
                               forKeyPath:@"selectionIndexes"];
    [super dealloc];
}

Still searching for the highlight resolution...

Update 2

Took Macatomy's advice but still had an issue. Posting the relevant class methods to see where I've gone wrong.

MyView.h

#import <Cocoa/Cocoa.h>

@interface MyView : NSView {
    BOOL selected;
}

@property (readwrite) BOOL selected;

@end

MyView.m

#import "MyView.h"

@implementation MyView

@synthesize selected;

-(id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code here.
    }
    return self;
}

-(void)drawRect:(NSRect)dirtyRect
{
    NSRect outerFrame = NSMakeRect(0, 0, 143, 104);
    NSRect selectedFrame = NSInsetRect(outerFrame, 2, 2);

    if (selected)
        [[NSColor yellowColor] set];
    else
        [[NSColor redColor] set];

    [NSBezierPath strokeRect:selectedFrame];
}

@end

MyCollectionViewItem.h

#import <Cocoa/Cocoa.h>
@class MyView;

@interface MyCollectionViewItem : NSCollectionViewItem {

}

@end

"MyCollectionViewItem.m*

#import "MyCollectionViewItem.h"
#import "MyView.h"

@implementation MyCollectionViewItem

-(void)setSelected:(BOOL)flag
{

    [(MyView *)[self view] setSelected:flag];
    [(MyView *)[self view] setNeedsDisplay:YES];
}

@end
+2  A: 

Its not too hard to do. Make sure "Selection" is enabled for the NSCollectionView in Interface Builder. Then in the NSView subclass that you are using for your prototype view, declare a property called "selected" :

@property (readwrite) BOOL selected;

UPDATED CODE HERE: (added super call)

Subclass NSCollectionViewItem and override -setSelected:

- (void)setSelected:(BOOL)flag
{
    [super setSelected:flag];
    [(PrototypeView*)[self view] setSelected:flag];
    [(PrototypeView*)[self view] setNeedsDisplay:YES];
}

Then you need to add code in your prototype view's drawRect: method to draw the highlight:

- (void)drawRect:(NSRect)dirtyRect 
{
    if (selected) {
       [[NSColor blueColor] set];
       NSRectFill([self bounds]);
    }
}

That just simply fills the view in blue when its selected, but that can be customized to draw the highlight any way you want. I've used this in my own apps and it works great.

macatomy
Macatomy, definitely a "Doh!" moment reading your solution, but after trying your suggestion had no luck - which suggests that I maybe have a problem with my PrototypeView. I can capture the selection event with the KVO, so I know the collectionView (and subsequently the array controller) is picking up the selection - and I can query the selected model object, but I am not getting `setSelected` called. `selected` returns false every time. Any idea? I'll post the classes if that would help.
Hooligancat
Make sure that in Interface Builder your collection view item and the prototype view have their class identity set to the proper subclass rather than just the default NSCollectionViewItem and NSView. If you've done this, then yes, posting the classes would be helpful :)
macatomy
I'm an idiot. I forgot to set the class identified for the CollectionViewItem in IB...
Hooligancat
Its a commonly overlooked mistake :)
macatomy
It is... unfortunately I still have an issue where my selection is now not being unselected. I have amended the description to show the code. Thanks again for your help...
Hooligancat
Sorry I made a mistake in the code. In the setSelected method of the collection view item there should be a [super setSelected:flag] at the beginning. Code updated in my answer.
macatomy
Sweet! All is well that ends well. Still can't believe I missed the class identifier in IB though... Thanks again! Very much appreciated!
Hooligancat
Glad it worked out
macatomy
@Macatomy, @Hooligancat: You should use `[NSColor alternateSelectedControlColor]`, not a hard-coded color like `[NSColor blueColor]`. Some of us prefer something other than blue for our selection highlights.
Peter Hosey
Yeah I wasn't intending the OP to use that as is, just an example on how to implement drawing based on whether the view is selected or not. But good point :)
macatomy
Good point Peter. I actually ended up using a user-adjustable highlight color set through preferences. It's all going into a pro-video application so the default color schemes tend not to work (as per Apple's interface guidelines video apps should have dark backgrounds), so as not to limit the user I've allowed preference settings in which the user can use the default OS selection colors or choose their own.
Hooligancat