Reputation: 26345
I have an NSCollectionView
that is showing some images. I have implemented an NSCollectionViewDelegate
to tell it which items should be selected and/or highlighted. I'm using a stock NSCollectionViewItem
to draw the images and their names. When the user selects an item, my delegate gets the messages about highlight state changes:
- (void)collectionView:(NSCollectionView *)collectionView
didChangeItemsAtIndexPaths:(NSSet<NSIndexPath *> *)indexPaths
toHighlightState:(NSCollectionViewItemHighlightState)highlightState
{
[collectionView reloadItemsAtIndexPaths:indexPaths];
}
I do a similar thing for didSelect
/didDeselect
:
- (void)collectionView:(NSCollectionView *)collectionView
didSelectItemsAtIndexPaths:(nonnull NSSet<NSIndexPath *> *)indexPaths
{
[collectionView reloadItemsAtIndexPaths:indexPaths];
}
In the NSCollectionViewItem
s view
, I do the following:
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
NSColor* bgColor = [[self window] backgroundColor];
NSColor* highlightColor = [NSColor selectedControlColor];
NSRect frame = [self bounds];
NSCollectionViewItemHighlightState hlState = [collectionViewItem highlightState];
BOOL selected = [collectionViewItem isSelected];
if ((hlState == NSCollectionViewItemHighlightForSelection) || (selected))
{
[highlightColor setFill];
}
else
{
[bgColor setFill];
}
[NSBezierPath fillRect:frame];
}
The problem I'm seeing is that drawing the highlight or selection appears to be random. When it does draw the selection, it's almost always on the items the user has actually selected (though it often leaves off the last item for some reason). Occasionally, it will select a different item the user did not click on or drag over. Often, though, it just doesn't draw.
I've added printing to verify that it is calling -didChangeItemsAtIndexPaths:toHighlightState:
and -didSelectItemsAtIndexPaths:
. Is there anything I'm doing wrong here?
I've added some logging to the view's -drawRect:
method, and it doesn't appear to be getting called on all transitions, even though I'm calling -reloadItemsAtIndexPaths:
in the -didChange*
methods. Why not?
I've also noticed that the delegate's -should/didDeselectItemsAtIndexPaths:
does not seem to get called ever, even though the -should/didSelectItemsAtIndexPaths:
does get called. Why is that?
Upvotes: 0
Views: 1878
Reputation: 26345
The problem turned out to be calling [collectionView reloadItemsAtIndexPaths:]
. When you do that, it removes the existing NSCollectionViewItem
and creates a new one (by calling your data source's collectionView:itemForRepresentedObjectAt:
). That immediately sets the new collection view item to not selected (or rather it doesn't set it to be selected). When that happens, it won't call your should/didDeselect
methods because the existing item doesn't exist anymore, and the new one is not selected.
The real solution turned out to be to subclass NSCollectionViewItem
and override -setSelected:
to do the following:
- (void)setSelected:(BOOL)selected
{
[super setSelected:selected];
[self.view setNeedsDisplay:YES];
}
When the view's -drawRect:
method gets called, it asks the item if it's selected and draws appropriately.
Therefore, I could completely remove all of the should/did/select/Deselect
methods from the delegate without any problem, and it all just worked!
Upvotes: 4