I'm implementing an application for macOS in which I use an NSCollectionView
as a sort of timeline. For this I use a custom subclass of NSCollectionViewFlowLayout
for the following reasons:
- I want the items to be scrollable horizontally only without wrapping to the next line
- Only
NSCollectionViewFlowLayout
seems to be capable of tellingNSCollectionView
to not scroll vertically (inspiration from here)
All of this works smoothly, however: I'm now trying to implement drag & drop reordering of the items. This also works, but I noticed that the inter item gap indicator (blue line) is not being displayed properly, even though I do return proper sizes. I calculate them as follows:
override func layoutAttributesForInterItemGap(before indexPath: IndexPath) -> NSCollectionViewLayoutAttributes?
{
var result: NSCollectionViewLayoutAttributes? = nil
// The itemLayoutAttributes dictionary is created in prepare() and contains
// the layout attributes for every item in the collection view
if indexPath.item < itemLayoutAttributes.count
{
if let itemLayout = itemLayoutAttributes[indexPath]
{
result = NSCollectionViewLayoutAttributes(forInterItemGapBefore: indexPath)
result?.frame = NSRect(x: itemLayout.frame.origin.x - 4, y: itemLayout.frame.origin.y, width: 3, height: itemLayout.size.height)
}
}
else
{
if let itemLayout = itemLayoutAttributes.reversed().first
{
result = NSCollectionViewLayoutAttributes(forInterItemGapBefore: indexPath)
result?.frame = NSRect(x: itemLayout.value.frame.origin.x + itemLayout.value.frame.size.width + 4, y: itemLayout.value.frame.origin.y, width: 3, height: itemLayout.value.size.height)
}
}
return result
}
Per documentation the indexPath
passed to the method can be between 0 and the number of items in the collection view including if we're trying to drop after the last item. As you can see I return a rectangle for the inter item gap indicator that should be 3 pixels wide and the same height as an item is.
While the indicator is displayed in the correct x-position with the correct width, it is only 2 pixels high, no matter what height I pass in.
How do I fix this?
NB: If I temporarily change the layout to NSCollectionViewFlowLayout
the indicator displays correctly.
I'm overriding the following methods/properties in NSCollectionViewFlowLayout
:
- prepare()
- collectionViewContentSize
- layoutAttributesForElements(in rect: NSRect) -> [NSCollectionViewLayoutAttributes]
- layoutAttributesForItem(at indexPath: IndexPath) -> NSCollectionViewLayoutAttributes?
- shouldInvalidateLayout(forBoundsChange newBounds: NSRect) -> Bool
- layoutAttributesForInterItemGap(before indexPath: IndexPath) -> NSCollectionViewLayoutAttributes?
You may also need to override
layoutAttributesForDropTarget(at pointInCollectionView: NSPoint) -> NSCollectionViewLayoutAttributes
.The documentation mentions that the frame of the attributes returned by this method
provides a bounding box for the gap, that will be used to position and size a drop target indicator
(view).You can set a breakpoint in your drag and drop code, after the drop indicator is drawn (e.g.
collectionView(:acceptDrop:indexPath:dropOperation:)
and then when the breakpoint is hit during a drag-and-drop session, run Xcode view debugger to examine the drop indicator view after it's added to the collection view. This way you can be sure the view hierarchy is correct and any issues are visually apparent.