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

And*_*sen 22 macos cocoa drag-and-drop objective-c 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.

MrG*_*mez 9

我觉得这样做很奇怪,但是这里的队列中有一个非常彻底的答案似乎已经被作者删除了.在其中,他们为工作解决方案提供了正确的链接,我认为需要将其作为其他人接受和运行的答案,如果他们愿意,可以将其包含在内.

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

行动画效果

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

enum {
    NSTableViewAnimationEffectFade = 0x1,
    NSTableViewAnimationEffectGap = 0x2,
};
Run Code Online (Sandbox Code Playgroud)

常量:

...

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++;
    }];

}
Run Code Online (Sandbox Code Playgroud)

我不确定它是否真的很简单,但如果它不符合你的需要,它至少值得检查和彻底反驳.

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


And*_*sen 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];
}
Run Code Online (Sandbox Code Playgroud)

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

- (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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,这是简化的代码,而不是我的程序的逐字复制/粘贴.我实际上最终将这些全部包含在使用代理委托和数据源对象的NSTableView子类中,因此上面的数据源/委托方法中的代码实际上在代理对真实委托和数据源的调用的拦截内.这样,真正的数据源和委托就不必做任何特殊的事情来获得差距开放行为.此外,桌面视图动画有时会出现一些小的瑕疵,这对于第一行上方的拖拽不起作用(没有间隙打开,因为没有行可以做得更高).总而言之,尽管有进一步改进的余地,但这种方法运作良好.

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

  • 嘿安德鲁,你可以在github上展示一个简单的示例项目吗? (3认同)

Rob*_*rto 6

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

[aTableView setDraggingDestinationFeedbackStyle:NSTableViewDraggingDestinationFeedbackStyleGap];

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