在拖放过程中打开NSTableView中的间隙

时间:2012-04-13 14:26:11

标签: objective-c macos cocoa drag-and-drop nstableview

我有一个简单的单列,基于视图的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 Dupe如果你也想看到它。更新:我要求的增强功能是在OS X 10.9 Mavericks中实现的,现在可以使用NSTableView API实现此行为。请参阅NSTableViewDraggingDestinationFeedbackStyleGap

4 个答案:

答案 0 :(得分:9)

我觉得这样做很奇怪,但是这里的队列中有一个非常彻底的答案似乎已被其作者删除了。在其中,他们提供the correct links to a working solution,我认为需要将其作为其他人接受和参与的答案,如果他们愿意的话,可以包含他们。

NSTableView的文档中,以下警告隐藏了行动画效果:

  

行动画效果

     

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

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

常量:

     

...

     

NSTableViewAnimationEffectGap

     

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

通过the example code from 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发布了a more complete answer,任何寻找同一问题解决方案的人都应该参考。

答案 1 :(得分: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建议的那样),而不是更改行高。

答案 2 :(得分:3)

实现所要求的一种方法是在建议的放置点(即两个最近的行之间)插入一个空行。这听起来好像你一直在使用NSTableViewAnimationEffectGap,正如你所注意到的那样,当-tableView:acceptDrop:row:dropOperation:中接受了放置时,它真正意味着动画插入。

由于您希望在之前打开间隙,用户释放鼠标按钮以实际执行放置,您可以使用表{{1}中的-insertRowsAtIndexes:withAnimation:插入空白行方法,同时使用-draggingUpdate:删除之前为此拖动插入的任何空白行。根据需要,使用-removeRowsAtIndexes:withAnimation:NSTableViewAnimationSlideUp作为这些操作的动画。

答案 3 :(得分:3)

从Mac OS X 10.9(Mavericks)开始,有一个更简单的解决方案来设置拖动和放大动画。放入NSTableView:

[aTableView setDraggingDestinationFeedbackStyle:NSTableViewDraggingDestinationFeedbackStyleGap];

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