Search code examples
cocoanscollectionviewnscollectionviewitem

Changing the selection behaviour of NSCollectionView


In my Mac app I have a NSCollectionView with multi select enabled. In my app being able to select more than one item is the norm, and having to press cmd while clicking to select multiple items is frustrating some users and most don't realise they can do it (I get a lot of feature requests asking for multi select).

So, I want to change the behaviour so that:

  • when a user clicks a second item, the first item remains selected (without the need for holding cmd)
  • When a user clicks a selected item, the item is deselected

I've tried overriding setSelected on my own subclass of NSCollectionViewItem like so:

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

Calling super setSelected is required to make sure the collection view functions correctly, but it also seems to be what is responsible for the default behaviour.

What should I do instead?


Solution

  • You could try intercepting all left-mouse-down events using a local events monitor. Within this block you'd then work out if the click happened on your collection view. If it did, create a new event which mimics the event you intercepted but add in the command key mask if it isn't already present. Then, at the end of the block return your event rather than the one you intercepted. Your collection view will behave as if the user had pressed the command key, even though they haven't!

    I had a quick go with this in a very simple demo app and it looks like a promising approach - though I expect you'll have to negotiate a few gotchas along the way.

    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    
    
        [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskFromType(NSLeftMouseDown) 
                handler:^NSEvent *(NSEvent *originalEvent) {
    
            // Did this left down event occur on your collection view? 
            // If it did add in the command key
    
            NSEvent *newEvent =
                [NSEvent
                    mouseEventWithType: NSLeftMouseDown
                    location: originalEvent.locationInWindow
                    modifierFlags: NSCommandKeyMask // I'm assuming it's not already present
                    timestamp: originalEvent.timestamp
                    windowNumber: originalEvent.windowNumber
                    context: originalEvent.context
                    eventNumber: originalEvent.eventNumber
                    clickCount: originalEvent.clickCount
                    pressure:0];
    
            return newEvent; // or originalEvent if it's nothing to do with your collection view
        }];
    }
    

    Edit (by question author):

    This solution is so heavily based on the original answer that this answer deserves credit (feel free to edit)

    You can also intercept the mouse event by subclassing the NSCollectionView class and overriding mousedown like this:

    @implementation MyCollectionView
    
    -(void) mouseDown:(NSEvent *)originalEvent {
    
        NSEvent *mouseEventWithCmd =
            [NSEvent
                mouseEventWithType: originalEvent.type
                location: originalEvent.locationInWindow
                modifierFlags: NSCommandKeyMask
                timestamp: originalEvent.timestamp
                windowNumber: originalEvent.windowNumber
                context: originalEvent.context
                eventNumber: originalEvent.eventNumber
                clickCount: originalEvent.clickCount
                pressure: originalEvent.pressure];
    
        [super mouseDown: mouseEventWithCmd];
    }
    
    @end