23

我有一个简单的、单列、基于视图的 NSTableView,其中包含可以拖动以重新排序的项目。在拖放过程中,我想让它在鼠标下方的位置打开要放置的项目的间隙。当您拖动以重新排序曲目时,GarageBand 会执行类似的操作(此处的视频:http ://www.screencast.com/t/OmUVHcCNSl )。据我所知,在 NSTableView 中没有内置支持。

有没有其他人尝试将此行为添加到 NSTableView 并找到一个好的解决方案?我已经想到并尝试了几种方法,但没有取得多大成功。我的第一个想法是在拖动期间通过发送-noteHeightOfRowsWithIndexesChanged:我的数据源的-tableView:validateDrop:...方法将鼠标下的行高加倍,然后返回正常高度的两倍-tableView:heightOfRow:。不幸的是,据我所知, NSTableView 在拖放过程中不会更新其布局,因此尽管调用noteHeightOfRowsWithIndexesChanged:了 ,但实际上并未更新行高。

请注意,我使用的是基于视图的 NSTableView,但我的行并不复杂,如果这样做有助于实现这一点,我无法移动到基于单元格的表视图。我知道在拖动完成后为放置的项目设置动画间隙的简单内置功能。我正在寻找一种在拖动过程中打开间隙的方法。此外,这是针对要在 Mac App Store 中销售的应用程序,因此不得使用私有 API。

编辑:我刚刚向 Apple 提交了一项增强请求,请求内置支持此行为: http: //openradar.appspot.com/12662624如果你也想看的话,请假装。更新:我要求的增强功能已在 OS X 10.9 Mavericks 中实现,现在可以使用 NSTableView API 实现此行为。请参阅NSTableViewDraggingDestinationFeedbackStyleGap

4

4 回答 4

9

我觉得这样做很奇怪,但这里的队列中有一个非常彻底的答案,似乎已被其作者删除。在其中,他们提供了一个工作解决方案的正确链接,我觉得需要将其作为答案提供给其他人,如果他们愿意的话,也包括他们。

从 的文档中NSTableView,对于行动画效果,隐藏了以下注意事项:

行动画效果

可选常量,指定 tableview 将使用淡入淡出来移除行或列。该效果可以与任何NSTableViewAnimationOptions常数组合。

enum {
    NSTableViewAnimationEffectFade = 0x1,
    NSTableViewAnimationEffectGap = 0x2,
};

常数:

...

NSTableViewAnimationEffectGap

为新插入的行创建一个间隙。这对于动画到新打开的间隙的拖放动画很有用,应该在委托方法中使用tableView:acceptDrop:row:dropOperation:

通过Apple 的示例代码,我发现:

- (void)_performInsertWithDragInfo:(id <NSDraggingInfo>)info parentNode:(NSTreeNode *)parentNode childIndex:(NSInteger)childIndex {
    // NSOutlineView's root is nil
    id outlineParentItem = parentNode == _rootTreeNode ? nil : parentNode;
    NSMutableArray *childNodeArray = [parentNode mutableChildNodes];
    NSInteger outlineColumnIndex = [[_outlineView tableColumns] indexOfObject:[_outlineView outlineTableColumn]];

    // Enumerate all items dropped on us and create new model objects for them    
    NSArray *classes = [NSArray arrayWithObject:[SimpleNodeData class]];
    __block NSInteger insertionIndex = childIndex;
    [info enumerateDraggingItemsWithOptions:0 forView:_outlineView classes:classes searchOptions:nil usingBlock:^(NSDraggingItem *draggingItem, NSInteger index, BOOL *stop) {
        SimpleNodeData *newNodeData = (SimpleNodeData *)draggingItem.item;
        // Wrap the model object in a tree node
        NSTreeNode *treeNode = [NSTreeNode treeNodeWithRepresentedObject:newNodeData];
        // Add it to the model
        [childNodeArray insertObject:treeNode atIndex:insertionIndex];
        [_outlineView insertItemsAtIndexes:[NSIndexSet indexSetWithIndex:insertionIndex] inParent:outlineParentItem withAnimation:NSTableViewAnimationEffectGap];
        // Update the final frame of the dragging item
        NSInteger row = [_outlineView rowForItem:treeNode];
        draggingItem.draggingFrame = [_outlineView frameOfCellAtColumn:outlineColumnIndex row:row];

        // Insert all children one after another
        insertionIndex++;
    }];

}

我不确定它是否真的这么简单,但如果它不能满足您的需求,它至少值得检查和彻底反驳。

编辑:请参阅此答案的评论,了解正确解决方案所遵循的步骤。OP 已经发布了一个更完整的答案,任何寻求相同问题解决方案的人都应该参考这个答案。

于 2012-04-17T00:40:35.593 回答
8

注意:这个问题和答案描述的行为现在可以在 OS X 10.9 Mavericks 及更高版本上使用 NSTableView 中的内置 API 获得。请参阅NSTableViewDraggingDestinationFeedbackStyleGap

如果在面向 OS X 10.8 或更早版本的应用程序中需要此行为,此答案可能仍然有用。原答案如下:

我现在已经实现了。我的基本方法如下所示:

@interface ORSGapOpeningTableView : NSTableView

@property (nonatomic) NSInteger dropTargetRow;
@property (nonatomic) CGFloat heightOfDraggedRows;

@end

@implementation ORSGapOpeningTableView

#pragma mark - Dragging

- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender
{
    NSInteger oldDropTargetRow = self.dropTargetRow;
    NSDragOperation result = [super draggingUpdated:sender];
    CGFloat imageHeight = [[sender draggedImage] size].height;
    self.heightOfDraggedRows = imageHeight;

    NSMutableIndexSet *changedRows = [NSMutableIndexSet indexSet];
    if (oldDropTargetRow > 0) [changedRows addIndex:oldDropTargetRow-1];
    if (self.dropTargetRow > 0) [changedRows addIndex:self.dropTargetRow-1];
    [self noteHeightOfRowsWithIndexesChanged:changedRows];

    return result;
}

- (void)draggingExited:(id<NSDraggingInfo>)sender
{
    self.dropTargetRow = -1;
    [self noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self numberOfRows])]];

    [super draggingExited:sender];
}

- (void)draggingEnded:(id<NSDraggingInfo>)sender
{
    self.dropTargetRow = -1;
    self.heightOfDraggedRows = 0.0;
    self.draggedRows = nil;
    [self noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self numberOfRows])]];
}

- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender
{
    self.dropTargetRow = -1;
    self.heightOfDraggedRows = 0.0;
    self.draggedRows = nil;
    [self noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self numberOfRows])]];

    return [super performDragOperation:sender];
}

// 在我的委托和数据源中:

- (NSDragOperation)tableView:(NSTableView *)tableView validateDrop:(id<NSDraggingInfo>)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)dropOperation
{
    if (dropOperation == NSTableViewDropOn) 
    {
        dropOperation = NSTableViewDropAbove;
        [self.tableView setDropRow:++row dropOperation:dropOperation];
    }

    NSDragOperation result = [self.realDataSource tableView:tableView validateDrop:info proposedRow:row proposedDropOperation:dropOperation];
    if (result != NSDragOperationNone) 
    {
        self.tableView.dropTargetRow = row;
    } 
    else 
    {
        self.tableView.dropTargetRow = -1; // Don't open a gap
    }
    return result;
}

- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
{
    CGFloat result = [tableView rowHeight];

    if (row == self.tableView.dropTargetRow - 1 && row > -1)
    {
        result += self.tableView.heightOfDraggedRows;
    }

    return result;
}

请注意,这是简化的代码,而不是我程序中的逐字复制/粘贴。实际上,我最终使这一切都包含在使用代理委托和数据源对象的 NSTableView 子类中,因此上述数据源/委托方法中的代码实际上是在代理对真实委托和数据源的调用的拦截中。这样,真正的数据源和委托不必做任何特殊的事情来获得间隙打开行为。此外,表格视图动画有时会有点不稳定,这不适用于第一行上方的拖动(没有打开间隙,因为没有行可以变得更高)。总而言之,尽管还有进一步改进的空间,但这种方法的效果相当不错。

我仍然想尝试类似的方法,但插入一个空白行(如 Caleb 建议的那样)而不是更改行高。

于 2012-04-28T21:12:04.513 回答
6

从 Mac OS X 10.9 (Mavericks) 开始,有一个更简单的解决方案可以在 NSTableView 中对拖放进行动画处理:

[aTableView setDraggingDestinationFeedbackStyle:NSTableViewDraggingDestinationFeedbackStyleGap];

当拖动一行时,表格视图将自动插入带有动画的间隙,这比旧的蓝线插入点方法要好得多。

于 2014-06-27T18:07:35.620 回答
3

完成您所要求的一种方法是在建议的放置点(即两个最近的行之间)插入一个空行。听起来您一直在研究 using NSTableViewAnimationEffectGap,正如您所注意到的,它实际上是用于在-tableView:acceptDrop:row:dropOperation:.

由于您想在用户释放鼠标按钮以实际进行拖放之前打开间隙,因此您可以使用-insertRowsAtIndexes:withAnimation:表的-draggingUpdate:方法插入一个空白行,同时删除您之前为此拖动插入的任何空白行-removeRowsAtIndexes:withAnimation:。酌情使用NSTableViewAnimationSlideUpNSTableViewAnimationSlideDown作为这些操作的动画。

于 2012-04-22T18:00:45.643 回答