Открытие пробела в NSTableView во время перетаскивания
У меня есть простой, одноколоночный, основанный на представлении NSTableView с элементами в нем, которые можно перетаскивать, чтобы переупорядочить их. Во время перетаскивания я хотел бы сделать так, чтобы в месте под мышкой открывался пробел для пункта-для-падения. GarageBand делает что-то подобное при перетаскивании для изменения порядка треков (видео здесь: http://www.screencast.com/t/OmUVHcCNSl). Насколько я могу судить, в NSTableView нет поддержки для этого.
Кто-нибудь еще пытался добавить это поведение в NSTableView и нашел хорошее решение? Я подумал и попробовал пару подходов без особого успеха. Моя первая мысль заключалась в том, чтобы удвоить высоту строки под мышью во время перетаскивания, отправив -noteHeightOfRowsWithIndexesChanged:
в мой метод источника данных -tableView:validateDrop:...
, а затем дважды вернув нормальную высоту в -tableView:heightOfRow:
. К сожалению, лучше всего могу сказать, что NSTableView не обновляет свой макет во время перетаскивания, поэтому, несмотря на вызов noteHeightOfRowsWithIndexesChanged:
, высота строки фактически не обновляется.
Обратите внимание, что я использую NSTableView, основанный на представлении, но мои строки не настолько сложны, что я не мог перейти к представлению таблицы на основе соты, если это помогло этому. Я знаю о простой, встроенной способности анимировать пробел для упавшего элемента после завершения перетаскивания. Я ищу способ открыть пробел, пока выполняется перетаскивание. Кроме того, это приложение должно быть продано в Mac App Store, поэтому он не должен использовать частный API.
EDIT: Я только что подал запрос на улучшение с Apple, запросив встроенную поддержку такого поведения: http://openradar.appspot.com/12662624. Dupe, если вы тоже захотите его увидеть. Обновление: исправление, которое я запросил, было реализовано в OS X 10.9 Mavericks, и это поведение теперь доступно с использованием API NSTableView. См. NSTableViewDraggingDestinationFeedbackStyleGap.
Ответы
Ответ 1
Примечание. Описание этого вопроса и ответа теперь доступно с использованием встроенного API в NSTableView в OS X 10.9 Mavericks и позже. См. 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, который использует делегированные делегаты и объекты источника данных, поэтому код в методах источника данных/делегирования выше находится внутри перехвата прокси вызовов вызовов реальному делегату и источнику данных. Таким образом, реальный источник данных и делегат не должны делать ничего особенного, чтобы получить поведение открытия пробела. Кроме того, иногда бывает немного flakiness с анимацией просмотра таблиц, и это не работает для перетаскивания над первой строкой (никакой пробел не открывается, поскольку нет строки, чтобы сделать выше). В целом, несмотря на возможности для дальнейшего совершенствования, этот подход работает достаточно хорошо.
Мне бы хотелось попробовать подобный подход, но вставить пустую строку (как предложил Калеб) вместо изменения высоты строки.
Ответ 2
Я чувствую себя странно для этого, но здесь очень сложный ответ в очереди, который, по-видимому, был удален его автором. В нем они предоставили правильные ссылки на рабочее решение, которые, как мне кажется, должны быть представлены как ответ для кого-то другого, который должен взять и запустить с включением их, если они этого желают.
Из документации для 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 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 опубликовал более полный ответ, на который должен ссылаться любой, кто ищет решения по той же проблеме.
Ответ 3
Один из способов выполнить то, что вы просите, - вставить пустую строку в предлагаемую точку падения (то есть между двумя ближайшими строками). Похоже, вы искали использование NSTableViewAnimationEffectGap
, которое, как вы заметили, действительно предназначено для анимации вставки, когда капля принимается в -tableView:acceptDrop:row:dropOperation:
.
Поскольку вы хотите открыть пробел до того, как пользователь освободит кнопку мыши, чтобы на самом деле сделать это, вы можете вместо этого вставить пустую строку с помощью -insertRowsAtIndexes:withAnimation:
из вашего метода таблицы -draggingUpdate:
и в то же время удалить пустую строку которую вы ранее вставляли для этого перетаскивания, используя -removeRowsAtIndexes:withAnimation:
. Используйте NSTableViewAnimationSlideUp
и NSTableViewAnimationSlideDown
в качестве анимации для этих операций, если это необходимо.
Ответ 4
Как и в Mac OS X 10.9 (Mavericks), гораздо проще решить анимировать перетаскивание в NSTableView:
[aTableView setDraggingDestinationFeedbackStyle: NSTableViewDraggingDestinationFeedbackStyleGap];
В представлении таблицы будут автоматически вставляться пробелы с анимацией по мере перетаскивания строки, что намного лучше, чем метод точки старинной синей линии.