Harish
Harish

Reputation: 2512

UICollectionView Custom Cell Refreshes automatically when i Scroll

I have an UICollectionView and Custom UICollectionViewCell, where i'm loading images, when i scroll the UICollectionView, i'm seeing all the cells are refreshing, here is the code for UICollectionView delegates,

In ViewDidLoad adding this first for adding CustomCell

 -(void)ViewdidLoad{
      UINib *nib = [UINib nibWithNibName:@"NMCFAIPadWishListCell" bundle:nil];
     [self.accountDetailsCollectionView registerNib:nib forCellWithReuseIdentifier:@"Cell"];
  }

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
      return [[self wishListData] count];

}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{

static NSString *identifier = @"Cell";

       NMCFAIPadWishListCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
      [cell setWishList:[[self wishListData] objectAtIndex:[indexPath row]] delegate:self];
      return cell;
}

In setWishList method just assigning the values from the array to label and i have a button in Xib for each cell in my custom UICollectionViewCell, when user taps on that button i'm just changing the label BG color

 - (void)setWishList:(NSString*)product delegate:(id)delegate
{
      self.label.text = product;
}

Below is the button action

- (IBAction)editProduct:(id)sender
{
      self.label.backgroundColor = [UIColor redColor];
}

Here my problem is when i scroll the Custom Cell and tap on Button in any Cell the label BG is not only changing in current cell but also in MANY CELLS.

Upvotes: 1

Views: 4023

Answers (4)

Chris
Chris

Reputation: 1673

Cells do not maintain a state. An array of objects that correspond to the cells should main the state since cells are recycled very often. For instance, inside you cellForItemAtIndexPath:

....

BOOL isWishListSet = self.isWishListSetArray[indexPath.row];
UIColor *cellColor = [UIColor redColor];
if (isWishListSet) {
    cellColor = [UIColor blackColor];
}
cell.backgroundColor = cellColor;

....

EDIT 1:

As gavdotnet mentions in his answer, cell states should be held in a parallel array, not in the cell itself. So you would have one array that holds the data you want to show and another that holds the state of whether the cell has been selected to be on the wishlist:

@interface WishListViewController ()

@property (nonatomic, strong) NSArray *wishListData;
@property (nonatomic, strong) NSMutableArray *wishListStatus;

@end

@implementation WishListViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Initialize arrays
    self.wishListData = [NSArray array];
    self.wishListStatus = [NSMutableArray array];
}

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return 1;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return self.wishListData.count;
}

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
    NSNumber *isWishListSet = self.wishListStatus[indexPath.row];
    UIColor *cellColor = [UIColor redColor];
    if (isWishListSet.boolValue) {
        cellColor = [UIColor blackColor];
    }
    cell.backgroundColor = cellColor;
    return cell;
}

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
    NSNumber *isWishListSet = self.wishListStatus[indexPath.row];
    if (isWishListSet.boolValue) {
        isWishListSet = [NSNumber numberWithBool:NO];
    } else {
        isWishListSet = [NSNumber numberWithBool:YES];
    }
    [self.wishListStatus replaceObjectAtIndex:indexPath.row withObject:isWishListSet];
    UIColor *cellColor = [UIColor redColor];
    if (isWishListSet.boolValue) {
        cellColor = [UIColor blackColor];
    }
    cell.backgroundColor = cellColor;
}

The section

UIColor *cellColor = [UIColor redColor];
if (isWishListSet.boolValue) {
    cellColor = [UIColor blackColor];
}
cell.backgroundColor = cellColor;

is repeated, so it should be in its own method, but that is up to you decide really. The example shows your data array, which populates the cells, and your wishListStatus array which holds the status of the cell. If we were not going to dequeue cells, this would not be an issue. But since we are in this case, the status must be maintained outside of the cell.

The line you are using:

[cell setWishList:[[self wishListData] objectAtIndex:[indexPath row]] delegate:self];

should be changed to something like:

[cell setDelegate:self];

since the delegate is never toggled and is always set to 'self'.

Upvotes: 1

quaertym
quaertym

Reputation: 3961

CollectionView reuses cells so the multiple change of background color is the correct behavior.

To fix your problem, customize your UICollectionViewCell(NMCFAIPadWishListCell) instance as follows:

UIView *backgroundView = [[UIView alloc] initWithFrame:self.bounds];
backgroundView.backgroundColor = [UIColor clearColor];
self.backgroundView = backgroundView;

UIView *selectedBGView = [[UIView alloc] initWithFrame:self.bounds];
selectedBGView.backgroundColor = [UIColor redColor];
self.selectedBackgroundView = selectedBGView;

Use the delegate method for extra selection behavior:

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath 
{
    // Instead of button actions, use this delegate method
}

Check out UICollectionViewCell Reference for more details. UICollectionViewCell has three properties backgroundView, selectedBackgroundView and selected which are sufficient for your needs.

Upvotes: 0

gavdotnet
gavdotnet

Reputation: 2224

You should not attempt to store any state in/on your cells as the cell objects themselves are reused at the discretion of the UICollectionView.

One solution to your problem could be:

  • In your editProduct: method (assuming your editProduct: method is in your custom UICollectionViewCell implementation), inform the collection view’s controller that the user has “selected” that product via a protocol method (or block or some other messaging mechanism).
  • In your view controller, when receiving the above message, identify the index of the cell for which the button has been tapped (indexPathForCell: might be useful here) and store the fact that the item at index n has been selected. An NSArray might be useful here.
  • In the same method, force a reload of the cell that has been tapped with reloadItemsAtIndexPaths: or a similar method. This will force the collectionView:cellForRowAtIndexPath: method to be called.
  • Implement something like the following in your collectionView:cellForRowAtIndexPath: method:

    BOOL itemSelected = ((NSNumber *)isProductSelectedArray[indexPath.row]).boolValue; // You can't store `BOOL`s directly into NSArrays. So I've assumed an NSNumber here.
    cell.backgroundColor = itemSelected ? [UIColor redColor] : [UIColor clearColor] // Or some other color to indicate non-selection.
    

As an aside, if you declare “ViewdidLoad” instead of “viewDidLoad”, you might find your code doesn’t behave the way you intend. Don’t forget to call [super viewDidLoad] somewhere in your implementation too.

Finally, I recommend getting a better handle on the concept of cell reuse by reading the “Collection View Basics” chapter of Apple’s “Collection View Programming Guide for iOS” - specifically the section titled “Reusable Views Improve Performance”.

Upvotes: 1

ManiaChamp
ManiaChamp

Reputation: 857

Cells are being reused because of that they are refreshing.

Upvotes: 0

Related Questions