【问题标题】:Extend selection in NSCollectionView with Shift key使用 Shift 键扩展 NSCollectionView 中的选择
【发布时间】:2016-05-22 10:40:36
【问题描述】:

我最近查看了我一年前发布的一个应用程序。 而且我看到现在它里面的NSCollectionView 已经失去了选择功能,比如 SHIFT + Select 现在它的行为就像 CMD + Select。

(次要问题:用鼠标拖动时我也没有得到选择矩形。)

显然我希望恢复此功能,使用 shift 会将选择范围从先前单击的单元格扩展到按 shift 单击的单元格。

我做了什么:

//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];

问题:有没有简单的方法来恢复这个功能?我在文档中没有看到任何相关内容,并且在互联网上找不到任何解决方案。

子问题:如果没有简单的方法来实现 -> 我是否应该继续创建自己的 FancyPrefix##CollectionViewClass 并按照我的意愿重新实现此功能 - 还是更好遍历现有的NSCollectionView 并强制它按我的意愿行事?

子注意:好吧,如果我发现自己重新实现它,它将是一个轻量级的,只会满足我自己的需求——我的意思是我不会模仿整个 NSCollectionView 类。

附言 我可以通过单击来选择一个项目我只能通过 CMD+Click 或 SHIFT+Click 选择多个项目,但后者的行为与我不想要的 CMD+Click 完全相同。

至于鼠标选择矩形 - 我没有覆盖任何鼠标事件。不清楚为什么我没有这个功能。

【问题讨论】:

  • 你有没有想过这个,我只是注意到我不能一次选择多个,除了一个......在我的情况下,矩形确实有效,只是不是 SHIFT-select
  • 不 :-(,我推出了自己的或子类的东西我不记得了,也许我明天会看看。
  • 我也实现了自己的子类。我检查了选择的第一个开始位置以及下一个项目的选择位置,并通过 shift-click 忽略了默认功能并手动选择了范围内的每个索引。

标签: objective-c swift macos cocoa nscollectionview


【解决方案1】:

似乎没有任何答案支持部分或跨部分边界进行转移选择。这是我的方法:

- (NSSet<NSIndexPath *> *)collectionView:(NSCollectionView *)collectionView shouldSelectItemsAtIndexPaths:(NSSet<NSIndexPath *> *)indexPaths
{
    if ([shotCollectionView shiftIsDown])
    {
        // find the earliest and latest index path and make a set containing all inclusive indices
        NSMutableSet* inclusiveSet = [NSMutableSet new];
        
        __block NSIndexPath* earliestSelection = [NSIndexPath indexPathForItem:NSUIntegerMax inSection:NSUIntegerMax];
        __block NSIndexPath* latestSelection = [NSIndexPath indexPathForItem:0 inSection:0];
        
        [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull obj, BOOL * _Nonnull stop) {
            
            NSComparisonResult earlyCompare = [obj compare:earliestSelection];

            NSComparisonResult latestCompare = [obj compare:latestSelection];

            if (earlyCompare == NSOrderedAscending)
            {
                earliestSelection = obj;
            }
            
            if (latestCompare == NSOrderedDescending)
            {
                latestSelection = obj;
            }
        }];
        
        NSSet<NSIndexPath *>* currentSelectionPaths = [shotCollectionView selectionIndexPaths];
        
        [currentSelectionPaths enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull obj, BOOL * _Nonnull stop) {
            
            NSComparisonResult earlyCompare = [obj compare:earliestSelection];

            NSComparisonResult latestCompare = [obj compare:latestSelection];

            if (earlyCompare == NSOrderedAscending)
            {
                earliestSelection = obj;
            }
            
            if (latestCompare == NSOrderedDescending)
            {
                latestSelection = obj;
            }
        }];
        
        NSUInteger earliestSection = [earliestSelection section];
        NSUInteger earliestItem = [earliestSelection item];
                                   
        NSUInteger latestSection = [latestSelection section];
        NSUInteger latestItem = [latestSelection item];
        
        for (NSUInteger section = earliestSection; section <= latestSection; section++)
        {
            NSUInteger sectionMin = (section == earliestSection) ? earliestItem : 0;
            NSUInteger sectionMax = (section == latestSection) ? latestItem : [self collectionView:collectionView numberOfItemsInSection:section];
            
            for (NSUInteger item = sectionMin; item <= sectionMax; item++)
            {
                NSIndexPath* path = [NSIndexPath indexPathForItem:item inSection:section];
                [inclusiveSet addObject:path];
            }
        }
        
        return inclusiveSet;
    }
    
    // Otherwise just pass through
    return indexPaths;
}

【讨论】:

    【解决方案2】:

    根据 Apple 的文档,shouldSelectItemsAtIndexPaths: 是仅在用户与视图交互时调用的事件,而不是在选择被自己的代码修改时调用。因此,这种方法避免了使用didSelectItemsAt:时可能出现的副作用。

    它的工作原理如下:

    1. 它需要记住之前点击过的项目。此代码假定NSCollectionView 实际上是一个名为CustomCollectionView 的子类,它有一个属性@property (strong) NSIndexPath * _Nullable lastClickedIndexPath;
    2. 仅在简单的选择单击时记住最后点击的项目,而不是例如具有多个选定项目的拖动选择。
    3. 如果检测到按住 Shift 键的第二次单选点击,它将选择从最后一次点击到当前点击范围内的所有项目。
    4. 如果取消选择某个项目,我们会清除最后点击的项目,以便更好地模拟预期行为。
    - (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;
    }
    

    此答案已被制作为“社区 wiki”,以便任何人都可以改进此代码。如果您发现错误或可以使其表现更好,请这样做。

    【讨论】:

      【解决方案3】:

      我编写了一个示例,从示例代码git@github.com:appcoda/PhotosApp.git 开始,我的所有更改都在 ViewController.swift 中。

      我通过添加来跟踪班次是向上还是向下

      var shiftDown = false
      override func flagsChanged(with event: NSEvent) {
          shiftDown = ((event.modifierFlags.rawValue >> 17) & 1) != 0
      }
      

      我添加了这个实例变量来记住最后的选择

      var lastIdx: IndexPath.Element?
      

      然后,对已经定义的collectionView(_:didSelectItemsAt),我添加了

      let thisIdx = indexPaths.first?[1]
      if shiftDown, let thisIdx = thisIdx, let lastIdx = lastIdx {
          let minItem = min(thisIdx, lastIdx)
          let maxItem = max(thisIdx, lastIdx)
          var additionalPaths = Set<IndexPath>()
          for i in minItem...maxItem {
              additionalPaths.insert(IndexPath(item: i, section: 0))
          }
          collectionView.selectItems(at: additionalPaths, scrollPosition: [])
      }
      lastIdx = thisIdx
      

      这只是一个开始。可能存在错误,尤其是在保存 lastIdx 时。

      【讨论】:

      • 我认为监视键盘修饰符不是一个好习惯。相反,您可能应该从 didSelectItemsAt 处理程序中的 NSEvent 获取它们。
      • 另外,我认为最好使用shouldSelectItemsAtIndexPaths:,而不是使用didSelectItemsAt:,因为这是专门为响应用户交互和修改用户在应用之前的选择。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多