Where to highlight UICollectionViewCell: delegate or cell?
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)
hpique
iOS, Android & Mac developer. Founder of Robot Media. @hpique
Updated on July 09, 2022Comments
-
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 thehighlighted
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 about 11 years+1 Thanks. I like the
setNeedsDisplay
+drawRect
approach. Maybe I would check ifself.highlighted != highlighted
(on line 1) before callingsetNeedsDisplay
, to avoid unnecessary drawing. -
bizz84 over 8 yearsYou don't need the
drawRect
andsetNeedsDisplay
calls when overridingsetHighlighted:
. This will do:self.contentView.backgroundColor = highlighted ? UIColor(white: 217.0/255.0, alpha: 1.0) : nil
-
A-Live over 8 years@bizz84 if you read the answer carefully that is explained there.
-
Joshua Hart almost 6 yearssetNeedsDisplay is not needed, draw rect is not needed. Just move your logic from the draw rect to the didSet.
-
Munib almost 6 yearsIf you have multiple cells and you scroll up and down, it selects random cells, is there a solution for that?
-
nikolovski almost 6 yearsVery clean implementation which avoids the
delaysContentTouches
problems. -
heyfrank over 4 yearsThis flickers on multiple cells in iOS 13 when coming back from detail screen :/...
-
heyfrank over 4 yearsNever mind, I was calling
collectionView.reloadData()
inviewDidAppear
🤦🏻♂️ -
Rui Peres about 4 yearsThe first solution is awesome. Thanks!
-
StackGU over 3 yearsbeautiful explanation I was searching for this to be clarified!