Apps like Preview indicate the drop position for drag operations by interactively moving the items out of the way. Looks like this:
IIRC, this is also the default behaviour of UICollectionView
with all standard layouts. In contrast, NSCollectionView
prefers to render an insertion indicator without repositioning the items, and I can’t quite figure out how to make it do the above. Interestingly, a screenshot in another question seems to show the exact behaviour I want (alas, the discussion over there is unrelated).
To clarify whether – and how – the delegate is set up for drag operations:
- (BOOL)collectionView:(NSCollectionView *)collectionView canDragItemsAtIndexPaths:(NSSet<NSIndexPath *> *)indexPaths withEvent:(NSEvent *)event {
return YES;
}
- (id<NSPasteboardWriting>)collectionView:(NSCollectionView *)collectionView pasteboardWriterForItemAtIndexPath:(NSIndexPath *)indexPath {
NSPasteboardItem *pasteboardItem = [[NSPasteboardItem alloc] init];
[pasteboardItem setString:@"Whatever is fine" forType:NSPasteboardTypeString];
return pasteboardItem;
}
- (NSDragOperation)collectionView:(NSCollectionView *)collectionView validateDrop:(id<NSDraggingInfo>)draggingInfo proposedIndexPath:(NSIndexPath * _Nonnull __autoreleasing *)proposedDropIndexPath dropOperation:(NSCollectionViewDropOperation *)proposedDropOperation {
*proposedDropOperation = NSCollectionViewDropBefore;
return NSDragOperationMove;
}
My initial understanding was that layout’s -layoutAttributesForDropTargetAtPoint:
/-layoutAttributesForInterItemGapBeforeIndexPath:
existed for this purpose. They don’t, as they only serve to place the indicator which does not affect the flow of items.
To be exact, setting an arbitrary rect (in a subclassed NSCollectionViewFlowLayout
):
- (NSCollectionViewLayoutAttributes *)layoutAttributesForDropTargetAtPoint:(NSPoint)pointInCollectionView {
NSCollectionViewLayoutAttributes *attributes = [[super layoutAttributesForDropTargetAtPoint:pointInCollectionView] copy];
attributes.frame = NSMakeRect(60, 0, 200, 200);
return attributes;
}
will render the indicator at {60, 0}, but that won’t affect the items in any way.
I could, of course, extend the layout further to make it aware of dragging sessions, but that already feels like a hack, and having to manually invalidate it during the drag (let alone doing it within -performBatchUpdates:completionHandler:
to trigger an animated response) leaves no doubt about that. Alternatively, I could manipulate items’ frames directly, but that also feels like fighting the NSCollectionView
rather than leveraging it.
Given how widespread the pattern is, there must be a proper way of implementing it without resorting to hacks. What am I missing?
Cheers.