Where to highlight UICollectionViewCell: delegate or cell?

37,742

Solution 1

As the documentation says, you can rely on highlighted property to be changed while the cell is highlighted. For example the following code will make the cell red when highlighted (not its subviews though):

- (void)setHighlighted:(BOOL)highlighted {
    [super setHighlighted:highlighted];
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];

    if (self.highlighted) {
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextSetRGBFillColor(context, 1, 0, 0, 1);
        CGContextFillRect(context, self.bounds);
    } 
}

And if you add something like this the background will become purple (red + opaque blue):

- (void)collectionView:(UICollectionView *)colView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [colView cellForItemAtIndexPath:indexPath];
    cell.contentView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:1 alpha:0.5];
}

- (void)collectionView:(UICollectionView *)colView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [colView cellForItemAtIndexPath:indexPath];
    cell.contentView.backgroundColor = nil;
}

So you can use both together (not necessarily both changing the cell appearance). The difference is that with delegate methods you also have indexPath. It might be used to create multi-selection (you will use this methods together with selection delegate methods), to show some preview while the cell is highlighted, to show some animation with other views... There's quite a few appliance for this delegate methods in my opinion.

As a conclusion, I would leave the cell appearance to be handled by the cell itself and use delegate methods to let controller make something cool in the same time.

Solution 2

Two possible approaches are outlined below.

Cell Subclassing

Cleaner approach if already subclassing from UICollectionViewCell.

class CollectionViewCell: UICollectionViewCell {

    override var highlighted: Bool {
        didSet {
            self.contentView.backgroundColor = highlighted ? UIColor(white: 217.0/255.0, alpha: 1.0) : nil
        }
    }
}

UICollectionViewDelegate

Less clean, requires the collection view delegate to know about the presentation logic of the cells.

func collectionView(collectionView: UICollectionView, didHighlightItemAtIndexPath indexPath: NSIndexPath) {

    if let cell = collectionView.cellForItemAtIndexPath(indexPath) {
        cell.contentView.backgroundColor = UIColor(white: 217.0/255.0, alpha: 1.0) // Apple default cell highlight color
    }
}


func collectionView(collectionView: UICollectionView, didUnhighlightItemAtIndexPath indexPath: NSIndexPath) {

    if let cell = collectionView.cellForItemAtIndexPath(indexPath) {
        cell.contentView.backgroundColor = nil
    }
}

Solution 3

Notice that UICollectionViewCell has a selectedBackgroundView property. By default, it's nil. Just create a view for this property, and it will appear when the user touches the cell.

override func awakeFromNib() {
    super.awakeFromNib()

    let view = UIView(frame: contentView.bounds)
    view.isUserInteractionEnabled = false
    view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    view.backgroundColor = UIColor(white: 0.94, alpha: 1.0)
    selectedBackgroundView = view
}

Solution 4

It is enough for highlighting cell (Swift 4)

class MyCollectionViewCell: UICollectionViewCell {
...
    override var isHighlighted: Bool {
        didSet {
            if isHighlighted {
                self.contentView.alpha = 0.6
            }
            else {
                self.contentView.alpha = 1.0
            }
        }
    }
}

Solution 5

Well...as all of these methods are correct. I've found the way that seems like the easiest one to me. Just override the setSelected: method (for example to change background color):

-(void)setSelected:(BOOL)selected{
    self.backgroundColor = selected?[UIColor greenColor]:[UIColor grayColor];
    [super setSelected:selected];
}

...it works "out of the box" (even with collectionView.allowsMultipleSelection)

Share:
37,742
hpique
Author by

hpique

iOS, Android & Mac developer. Founder of Robot Media. @hpique

Updated on July 09, 2022

Comments

  • hpique
    hpique almost 2 years

    According to the Collection View Programming Guide one should handle the visual state of the cell highlights in the UICollectionViewDelegate. Like this:

    - (void)collectionView:(PSUICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
    {
        MYCollectionViewCell *cell = (MYCollectionViewCell*)[collectionView cellForItemAtIndexPath:indexPath];
        [cell highlight];
    }
    
    - (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
    {
        MYCollectionViewCell *cell = (MYCollectionViewCell*)[collectionView cellForItemAtIndexPath:indexPath];
        [cell unhighlight];
    }
    

    What I don't like about this approach is that it adds logic to the delegate that is very specific to the cell. In fact, UICollectionViewCell manages its highlighted state independently, via the highlighted property.

    Wouldn't overriding setHighlighted: be a cleaner solution, then?

    - (void)setHighlighted:(BOOL)highlighted
    {
        [super setHighlighted:highlighted];
        if (highlighted) {
            [self highlight];
        } else {
            [self unhighlight];
        }
    }
    

    Are there any disadvantages to this approach instead of the delegate approach?

  • hpique
    hpique about 11 years
    +1 Thanks. I like the setNeedsDisplay + drawRect approach. Maybe I would check if self.highlighted != highlighted (on line 1) before calling setNeedsDisplay, to avoid unnecessary drawing.
  • bizz84
    bizz84 over 8 years
    You don't need the drawRect and setNeedsDisplay calls when overriding setHighlighted:. This will do: self.contentView.backgroundColor = highlighted ? UIColor(white: 217.0/255.0, alpha: 1.0) : nil
  • A-Live
    A-Live over 8 years
    @bizz84 if you read the answer carefully that is explained there.
  • Joshua Hart
    Joshua Hart almost 6 years
    setNeedsDisplay is not needed, draw rect is not needed. Just move your logic from the draw rect to the didSet.
  • Munib
    Munib almost 6 years
    If you have multiple cells and you scroll up and down, it selects random cells, is there a solution for that?
  • nikolovski
    nikolovski almost 6 years
    Very clean implementation which avoids the delaysContentTouches problems.
  • heyfrank
    heyfrank over 4 years
    This flickers on multiple cells in iOS 13 when coming back from detail screen :/...
  • heyfrank
    heyfrank over 4 years
    Never mind, I was calling collectionView.reloadData() in viewDidAppear 🤦🏻‍♂️
  • Rui Peres
    Rui Peres about 4 years
    The first solution is awesome. Thanks!
  • StackGU
    StackGU over 3 years
    beautiful explanation I was searching for this to be clarified!