Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extend selection in NSCollectionView with Shift key

I recently reviewed one of my Applications that I released a year ago. And I see that nowadays the NSCollectionView inside it has lost the selection functioning such as SHIFT + Select now it behaving as CMD + Select.

(Secondary issue: I am also not getting a selection rectangle when dragging with the mouse.)

Obviously I want this feature back, where using shift would expand the selection from the previously clicked cell to the shift-clicked cell.

What have I done:

//NSCollectionView * _picturesGridView; //is my iVar

//In initialization I have set my _picturesGridView as follows
//Initializations etc are omitted -- (only the selection related code is here)
[_picturesGridView setSelectable:YES];
[_picturesGridView setAllowsMultipleSelection:YES];

Question: Is there an easy way to get back this functionality? I don't see anything related in documentation and I couldn't find any solution on the internet.

Sub Question: If there is no easy way to achieve that -> Should I go ahead and create my own FancyPrefix##CollectionViewClass and to reimplement this feature as I wish -- Or is it better to go over the existing NSCollectionView and force it to behave as I wish?

Sub Note: Well if I will find myself reimplementing it it will be light weight class that will just comply to my own needs -- I mean I will not mimic the entire NSCollectionView class.

P.S. I am able to select an item by clicking on it I am able to select multiple items only with CMD+Click or SHIFT+Click but the latter behaves exactly as CMD+Click which I don't want as well.

As for the mouse Selection Rectangle - I didn't override any Mouse events. It is not clear why I don't have this functionality.

like image 881
ColdSteel Avatar asked Feb 11 '16 15:02

ColdSteel


1 Answers

Per Apple's documentation, shouldSelectItemsAtIndexPaths: is the event that only gets invoked when the user interacts with the view, but not when the selection is modified by one's own code. Therefore, this approach avoids side effects that may occur when using didSelectItemsAt:.

It works as follows:

  1. It needs to remember the previously clicked-on item. This code assumes that the NSCollectionView is actually a subclass called CustomCollectionView that has a property @property (strong) NSIndexPath * _Nullable lastClickedIndexPath;
  2. It only remembers the last clicked-on item when it was a simple selection click, but not, for instance, a drag-selection with more than one selected item.
  3. If it detects a second single-selection click with the Shift key down, it selects all the items in the range from the last click to the current click.
  4. If an item gets deselected, we clear the last clicked-on item in order to simulate better the intended behavior.
- (NSSet<NSIndexPath *> *)collectionView:(CustomCollectionView *)collectionView shouldDeselectItemsAtIndexPaths:(NSSet<NSIndexPath*> *)indexPaths
{
    collectionView.lastClickedIndexPath = nil;
    return indexPaths;
}

- (NSSet<NSIndexPath *> *)collectionView:(CustomCollectionView *)collectionView shouldSelectItemsAtIndexPaths:(NSSet<NSIndexPath*> *)indexPaths
{
    if (indexPaths.count != 1) {
        // If it's not a single cell selection, then we also don't want to remember the last click position
        collectionView.lastClickedIndexPath = nil;
        return indexPaths;
    }
    NSIndexPath *clickedIndexPath = indexPaths.anyObject;   // now there is only one selected item
    NSIndexPath *prevIndexPath = collectionView.lastClickedIndexPath;
    collectionView.lastClickedIndexPath = clickedIndexPath; // remember last click
    BOOL shiftKeyDown = (NSEvent.modifierFlags & NSEventModifierFlagShift) != 0;
    if (NOT shiftKeyDown || prevIndexPath == nil) {
        // not an extension click
        return indexPaths;
    }
    NSMutableSet<NSIndexPath*> *newIndexPaths = [NSMutableSet set];
    NSInteger startIndex = [prevIndexPath indexAtPosition:1];
    NSInteger endIndex = [clickedIndexPath indexAtPosition:1];
    if (startIndex > endIndex) {
        // swap start and end position so that we can always count upwards
        NSInteger tmp = endIndex;
        endIndex = startIndex;
        startIndex = tmp;
    }
    for (NSInteger index = startIndex; index <= endIndex; ++index) {
        NSUInteger path[2] = {0, index};
        [newIndexPaths addObject:[NSIndexPath indexPathWithIndexes:path length:2]];
    }
    return newIndexPaths;
}

This answer has been made "Community wiki" so that anyone may improve this code. Please do so if you find a bug or can make it behave better.

like image 65
Thomas Tempelmann Avatar answered Oct 26 '22 06:10

Thomas Tempelmann