2012-04-13 55 views
22

我已經有了一個簡單的,單列的,基於視圖的NSTableView,其中的項目可以拖動來對它們進行重新排序。在拖放過程中,我希望這樣做的目的是在鼠標下方的位置打開要刪除項目的間隙。當您拖動以重新排列曲目時,GarageBand會做類似的事情(視頻在這裏:http://www.screencast.com/t/OmUVHcCNSl)。據我所知,在NSTableView中沒有內建的支持。在拖放期間打開NSTableView中的空白

有沒有其他人試圖將此行爲添加到NSTableView並找到一個好的解決方案?我想到並嘗試了一些方法,但沒有取得太大的成功。我的第一個想法是通過在我的數據源的-tableView:validateDrop:...方法中發送-noteHeightOfRowsWithIndexesChanged:,然後在-tableView:heightOfRow:的範圍內返回正常高度的兩倍,來在拖動過程中將鼠標下方的行高度加倍。不幸的是,最好的我可以告訴,NSTableView在拖放過程中不會更新它的佈局,所以儘管調用了noteHeightOfRowsWithIndexesChanged:,但行高並沒有實際更新。

請注意,我正在使用基於視圖的NSTableView,但我的行並不那麼複雜,以至於我無法移動到基於單元格的表視圖,如果這樣做有助於完成此操作。我意識到在拖動完成後爲拖放項目設置間隙的簡單內置功能。我正在尋找一種方法來在拖動正在進行時打開間隙。此外,這是爲了在Mac App Store中銷售應用程序,因此它不得使用私有API。

編輯:我剛剛提出了一個增強請求與蘋果請求內置支持此行爲:http://openradar.appspot.com/12662624 杜佩如果你也想看到它。 更新:我請求的增強功能是在OS X 10.9 Mavericks中實現的,現在使用NSTableView API可以實現此行爲。見NSTableViewDraggingDestinationFeedbackStyleGap

+0

我想知道如何做到這一點! – Vervious 2012-04-13 17:40:42

+0

我不確定這是否會起作用,但是如何在移動時插入空行? – lnafziger 2012-04-16 06:11:46

+0

@Inafziger我也想到了,但我認爲「不幸的是,最好的我可以告訴,NSTableView不會在拖放過程中更新它的佈局」,這是行不通的。 – mydogisbox 2012-04-16 15:06:44

回答

7

注意:此問答描述的行爲現在可在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建議)而不是更改行高。

+3

嘿安德魯,任何方式,你可以拋出一個簡單的示例項目在github上顯示這個? – coneybeare 2012-11-15 01:54:50

+0

這個示例代碼中的'self.realDataSource'是什麼? – Kyle 2013-07-25 23:19:55

+0

ORSGapOpeningTableView.m中有「代理」數據源和委託對象。它們用於攔截NSTableView發送到其自己的數據源和委託的消息,以便無論(外部)數據源/委託如何都可以修改結果。 self.realDataSource是真實的(即外部設置的)數據源,而不是代理。 – 2013-07-26 03:08:52

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

} 

我不能確定它是否真的這樣簡單,但如果它不能滿足你的需要它至少值得檢查和徹底refutal。

編輯:請參閱此答案的評論,瞭解正確解決方案的步驟。 OP已發佈a more complete answer,任何尋求解決相同問題的人都應該參考。

+1

感謝您的回答MrGomez。我對答案者刪除這個答案的原因的猜測是,我在我的問題中提到我意識到這一點,而這不是我正在尋找的。這是爲了在物品掉落之後動畫化一個間隙*,而不是在拖動過程中發生(移動)間隙。如果我不知道如何去做我真正想要的,我實際上已經在使用這個解決方案作爲權宜之計。 – 2012-04-17 04:24:12

+0

@AndrewMadsen謝謝你的回覆。我曾認爲這是事實,但我想確保這一點能夠澄清,以供任何人直接尋找這個問題。我會尋找更好的選擇來更加優雅地解決這個問題,現在,將其作爲其他潛在答覆者遵循的標誌。畢竟:明確知道你所嘗試和駁斥的內容應該會帶來更好的答案。 :) – MrGomez 2012-04-17 05:18:19

+1

@AndrewMadsen由於到目前爲止沒有人回答你的問題,我已經走了一步,挖了一點,看看我能否自己回答。我在[本文檔節點](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/DragandDrop/Tasks/acceptingdrags.html)中發現,您可以實現['draggingUpdated:'](https ://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Protocols/NSDraggingDestination_Protocol/Reference/Reference.html)來獲取拖動操作發送器,並從那裏獲取當前指針位置。 – MrGomez 2012-04-20 20:45:17

3

完成要求的一種方法是在建議的放置點(即兩個最近的行之間)插入一個空行。這聽起來像你一直在尋找使用NSTableViewAnimationEffectGap,正如你注意到,這是真的意味着當在-tableView:acceptDrop:row:dropOperation:接受放置時動畫插入。

既然要開拓用戶鬆開鼠標按鈕,實際上做的下降之前的差距,你可以改爲插入使用-insertRowsAtIndexes:withAnimation:從表中的-draggingUpdate:方法,並在同一時間刪除任何空行空行您以前使用-removeRowsAtIndexes:withAnimation:插入此拖動。酌情使用NSTableViewAnimationSlideUpNSTableViewAnimationSlideDown作爲這些操作的動畫。

+1

Caleb,非常感謝答案。實際上我並沒有專注於NSTableViewAnimationEffectGap,我只提到我知道它並且沒有按照我想要的去做(見我對戈麥斯的回答的第一個評論)。根據MrGomez的評論,我嘗試了一種類似於你所描述的方法,它會起作用。所有這一切的關鍵是在NSTableView子類中重寫「-draggingUpdated:」。如果您嘗試在數據源的'-tableView:validateDrop:...'方法(它被' - [NSTableView draggingUpdated:]'間接調用)執行類似的操作,它將不起作用。 – 2012-04-22 19:14:42

2

由於Mac OS X的10.9(小牛)的,有一個更容易解決動畫在NSTableView的拖動&降:

[aTableView setDraggingDestinationFeedbackStyle:NSTableViewDraggingDestinationFeedbackStyleGap];

表格視圖會自動在動畫中插入空白,因爲拖動的行比舊的藍色插入點方法好得多。