Подтвердить что ты не робот

Открытие пробела в 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.

4b9b3361

Ответ 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];

В представлении таблицы будут автоматически вставляться пробелы с анимацией по мере перетаскивания строки, что намного лучше, чем метод точки старинной синей линии.