objective-c macos cocoa drag-and-drop nstableview

objective c - Abrir un espacio en NSTableView durante la operación de arrastrar y soltar



objective-c macos (4)

Tengo un NSTableView simple, de una sola columna, basado en vistas, con elementos que se pueden arrastrar para reordenarlos. Durante la operación de arrastrar y soltar, me gustaría hacer que se abra un espacio para el elemento que se va a colocar en la ubicación debajo del mouse. GarageBand hace algo como esto cuando arrastra para reordenar las pistas (video aquí: http://www.screencast.com/t/OmUVHcCNSl ). Por lo que puedo decir, no hay soporte integrado para esto en NSTableView.

¿Alguien más ha intentado agregar este comportamiento a NSTableView y ha encontrado una buena solución? He pensado y probado un par de enfoques sin mucho éxito. Mi primer pensamiento fue duplicar la altura de la fila debajo del mouse durante un arrastre enviando el -noteHeightOfRowsWithIndexesChanged: en mi -tableView:validateDrop:... de fuentes de datos -tableView:validateDrop:... , y luego devolviendo dos veces la altura normal en -tableView:heightOfRow: Desafortunadamente, lo mejor que puedo decir es que NSTableView no actualiza su diseño durante la operación de arrastrar y soltar, así que a pesar de llamar noteHeightOfRowsWithIndexesChanged: la altura de la fila no está actualizada.

Tenga en cuenta que estoy usando un NSTableView basado en vistas, pero mis filas no son tan complejas que no podría moverme a una vista de tabla basada en celdas si hacerlo ayudara a lograrlo. Soy consciente de la capacidad incorporada y fácil de animar un espacio para el elemento que se ha caído una vez que se completa un arrastre. Estoy buscando una manera de abrir un espacio mientras el arrastre está en progreso. Además, esto es para que una aplicación se venda en la Mac App Store, por lo que no debe usar API privada.

EDITAR: Acabo de presentar una solicitud de mejora con Apple solicitando soporte integrado para este comportamiento: http://openradar.appspot.com/12662624 . Dupe si te gustaría verlo también. Actualización: la mejora que solicité se implementó en OS X 10.9 Mavericks, y este comportamiento ahora está disponible mediante la API NSTableView. Ver NSTableViewDraggingDestinationFeedbackStyleGap .


A partir de Mac OS X 10.9 (Mavericks), hay una solución mucho más sencilla para animar arrastrar y soltar en un NSTableView:

[aTableView setDraggingDestinationFeedbackStyle: NSTableViewDraggingDestinationFeedbackStyleGap];

La vista de tabla insertará automáticamente espacios con la animación a medida que se arrastra una fila, lo que es mucho más agradable que el antiguo método del punto de inserción de la línea azul.


Me siento extraño por hacer esto, pero hay una respuesta muy completa en la cola aquí que parece haber sido eliminada por su autor. En él, proporcionaron los enlaces correctos a una solución de trabajo , que creo que se deben presentar como una respuesta para que otra persona los tome y corra, incluso si desean hacerlo.

De la documentación de NSTableView , las siguientes advertencias están NSTableView para los efectos de animación de la fila:

Efectos de animación de fila

Constante opcional que especifica que la vista de tabla usará un fundido para la eliminación de filas o columnas. El efecto se puede combinar con cualquier constante NSTableViewAnimationOptions .

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

Constantes:

...

NSTableViewAnimationEffectGap

Crea un espacio para las filas recién insertadas. Esto es útil para las animaciones de arrastrar y soltar que se animan a un espacio abierto recientemente y se deben usar en el método delegado tableView:acceptDrop:row:dropOperation:

Pasando por el código de ejemplo de Apple , encuentro esto:

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

No estoy seguro de que sea realmente así de simple, pero al menos merece la pena una inspección y un rechazo directo si no satisface sus necesidades.

Editar: vea los comentarios de esta respuesta para los pasos seguidos hacia la solución correcta. El OP ha publicado una respuesta más completa , que debe ser consultada por cualquiera que busque soluciones para el mismo problema.


Nota: El comportamiento que describen esta pregunta y respuesta ahora está disponible mediante la API incorporada en NSTableView en OS X 10.9 Mavericks y versiones posteriores. Ver NSTableViewDraggingDestinationFeedbackStyleGap .

Esta respuesta aún puede ser útil si este comportamiento es necesario en una aplicación dirigida a OS X 10.8 o anterior. Respuesta original a continuación:

He implementado esto ahora. Mi enfoque básico se ve así:

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

// En mi delegado y fuente de datos:

- (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; }

Tenga en cuenta que este es un código simplificado, no una copia / pegado textual de mi programa. De hecho, terminé haciendo todo esto contenido dentro de una subclase NSTableView que utiliza objetos de fuente de datos y delegado de proxy, por lo que el código en la fuente de datos / métodos de delegado anteriores está realmente dentro de la intersección de las llamadas a la fuente de datos y delegado real. De esa manera, la fuente de datos real y el delegado no tienen que hacer nada especial para obtener el comportamiento de apertura de brecha. Además, a veces hay un poco de descamación con las animaciones de la vista de tabla, y esto no funciona para los arrastres por encima de la primera fila (no se abre ningún espacio ya que no hay fila para hacer más alto). En general, a pesar del margen de mejora adicional, este enfoque funciona razonablemente bien.

Todavía me gustaría intentar un enfoque similar, pero inserte una fila en blanco (como sugirió Caleb) en lugar de cambiar la altura de la fila.


Una forma de lograr lo que está pidiendo es insertar una fila vacía en el punto de entrega propuesto (es decir, entre las dos filas más cercanas). Parece que ha estado usando NSTableViewAnimationEffectGap , que como nota realmente está destinado a animar la inserción cuando se acepta la -tableView:acceptDrop:row:dropOperation: en -tableView:acceptDrop:row:dropOperation:

Ya que desea abrir la brecha antes de que el usuario suelte el botón del mouse para hacer la caída, en lugar de eso, puede insertar una fila en blanco usando -insertRowsAtIndexes:withAnimation: del método -insertRowsAtIndexes:withAnimation: de su tabla y al mismo tiempo eliminar cualquier fila en blanco Usted insertó previamente para este arrastre usando -removeRowsAtIndexes:withAnimation: Utilice NSTableViewAnimationSlideUp y NSTableViewAnimationSlideDown como las animaciones para estas operaciones, según corresponda.