12

I have a UICollectionView with labels inside the cells that change automatically periodically. When this update triggers I call reloadData on the UICollectionView and I have set the cells to change the background colour on [UICollectionViewCell setHighlighted:].

The problem is if a user holds down on a cell then the update happens, when the user releases the cell stays highlighted and also cannot be selected anymore.

I have noticed that dequeueReusableCellWithReuseIdentifier:forIndexPath: calls setHighlighted on the cells after reloadData.

I have also tried reloadSections: instead of reloadData, this fixes the problem of the cells getting 'stuck', but causes a fade out and in on the cells when ever its called.

Placing the calls inside performBatchUpdates: doesn't seem to fix the problem either.

Prasad Devadiga
  • 2,573
  • 20
  • 43
richy
  • 2,716
  • 1
  • 33
  • 42
  • Did you implement the **didUnhighlightItemAtIndexPath:** to change cell background color?? – Anil Varghese May 24 '13 at 09:20
  • It doesn't seem to matter if the background colour is set or not, the cell still becomes unselectable. – richy May 30 '13 at 23:36
  • 1
    this seemed to prevent the fade in/out on `reloadSections:` http://stackoverflow.com/a/15068865/667834 – richy May 31 '13 at 00:04

5 Answers5

7

Inside the cell's class try calling:

- (void)prepareForReuse {
    [super prepareForReuse];
    [self setHighlighted:NO];
    ... Any other custom stuff that should be cleaned up ...
}

The issue is that you are probably doing background coloring differently then the cell would normally do on its self when highlighted. Normally the cell's superclass would undo those changes in prepareForReuse, but it doesn't know about your changes.

jamone
  • 17,253
  • 17
  • 63
  • 98
  • Cleaning custom stuff in prepareForReuse is obviously a must-do for UICollectionViewCell. But I fail to see how your answer will help to re-highlight cell and fix the problem with cell being "stacked" after reloadData call. – Alexander Dvornikov Feb 06 '14 at 11:44
  • @AlexanderDvornikov: Is it right that you want the cell to stay selected by the user after a reload? or can we set the cell to deselected state and the user has to select them again? – zero3nna Feb 07 '14 at 09:47
  • You're right, I expect the same behavior as in UITableView - when tableView is reloaded pressed cell should stays highlighted. Basically, you can find the very same bug in Google Drive or eBay apps - when user highlights cell after trigging pull-to-refresh, content collection view reloads and pressed cell stays both highlighted and stacked. – Alexander Dvornikov Feb 07 '14 at 10:37
2

I used the following as a workaround:

// Both highlightedData and lastHighlightedData are only needed if you want to prevent user from selecting a cell which data changed during the reload. If not needed, a boolean may be used instead
@property (nonatomic) id highlightedData;
@property (nonatomic) id lastHighlightedData;
@property (nonatomic) BOOL pendingCollectionViewReload;

// Wrap the reloadData call. Enqueue it if there's a highlighted cell:
- (void)reloadCollectionView
{
    if (self.highlightedData) {
        self.pendingCollectionViewReload = YES;
        return;
    }

    [self.collectionView reloadData];
    self.pendingCollectionViewReload = NO;
}

// When a cell is highlighted, save its index, or better the related data:
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
    // Save data at indexPath to self.highlightedData
}    

// Then reload the data when the cell is unhighlighted:
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
    self.lastHighlightedData = self.highlightedData;
    self.highlightedData = nil;

    if (self.pendingCollectionViewReload) {
        [self reloadCollectionView];
    }
}

// The following can be used from shouldPerformSegueWithIdentifier or didSelectItemAtIndexPath to prevent actions if the data associated with the indexPath changed:
- (BOOL)selectedDataEquals:(id)data
{
    // I used lastHighlightedData in addition to highlightedData because this function may be used after the didUnhighlightItemAtIndexPath was called:
    return (self.highlightedData && self.highlightedData == data) || (!self.highlightedData && self.lastHighlightedData == data);
}
silyevsk
  • 4,021
  • 3
  • 31
  • 30
  • This method worked for me -- simply prevent the collection view from reloading while an item is highlighted, and reload if needed after the item becomes unhighlighted. – titaniumdecoy Oct 04 '14 at 00:09
2

I encountered the same problem in a collection view that didn't need highlighting and I noticed that the didHighlight/didUnhighlight methods behaved correctly, so I ended up blocking reloads while a touch was in progress, using something like this:

BOOL blockColviewUpdate,colviewUpdateQueued;

and then

-(void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
    blockColviewUpdate=YES;
}

-(void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
    blockColviewUpdate=NO;
    if(colviewUpdateQueued==YES) [self CollectionViewRefresh];
}

while using an own function instead of calling reloadData directly:

-(void)CollectionViewRefresh
{
    if(blockColviewUpdate==YES) colviewUpdateQueued=YES;
    else
    {
        colviewUpdateQueued=NO;
        [self.colview reloadData];
    }
}

It helped in my case and no reloads were lost.

mpramat
  • 201
  • 2
  • 5
0

I am using collection views in one of my recent projects and the following code works fine for me.

    - (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:    (NSIndexPath *)indexPath
{
    return YES;
}

- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath;
{
    return YES;
}

In the collectionviewcell subclass

@interface SalesLegendCell : UICollectionViewCell

below is the code

    - (id)initWithFrame:(CGRect)frame {    
    self = [super initWithFrame:frame];    
    if (self) {
        // Initialization code
        NSArray *arrayOfViews = [[NSBundle mainBundle] loadNibNamed:@"SalesLegendCell" owner:self options:nil];

        if ([arrayOfViews count] < 1) {
            return nil;
        }

        if (![[arrayOfViews objectAtIndex:0] isKindOfClass:[SalesLegendCell class]]) {
            return nil;
        }

        self = [arrayOfViews objectAtIndex:0];

        UIView* backgroundView = [[UIView alloc] initWithFrame:self.bounds];
        backgroundView.backgroundColor = [UIColor colorWithWhite:0.85 alpha:1];

        UIView* selectedBGView = [[UIView alloc] initWithFrame:self.bounds];
        selectedBGView.backgroundColor = [UIColor colorWithWhite:1.0 alpha:1];

        self.selectedBackgroundView = backgroundView;
        self.backgroundView = selectedBGView;        
    }
    return self;
}
Rajesh
  • 850
  • 9
  • 18
0

Possibly unrelated, but this bit me: UIImage instances rendered on a UIViewCollectionCell in .alwaysTemplate mode get by default highlighted.

Fran Pugl
  • 464
  • 5
  • 6