Reusing cells UICollectionView - some cells don't get selected - ios

In the UICollectionView, I've got a custom UICollectionViewCellClass, where prepareForReuse is overridden for default formatting staff.
I've got an NSMutableArray containing NSIndexPaths from didSelectItemAtIndexPath:.
In cellForItemAtIndexPath: I reformat the selected cells so they appear selected.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
ButtonCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"ButtonCell" forIndexPath:indexPath];
NSString *title = self.ingredientsBook.names[indexPath.item];
cell.label.text = title;
if ([self isSelectedIndexPath:indexPath]){
cell.backgroundColor = [UIColor whiteColor];
cell.label.textColor = [UIColor blueColor];
}
return cell;
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
self.searchButton.enabled = YES;
ButtonCell *cell = (ButtonCell *)[collectionView cellForItemAtIndexPath:indexPath];
[selectedCellIndexPaths addObject:indexPath];
NSLog(#"%#", selectedCellIndexPaths);
cell.backgroundColor = [UIColor whiteColor];
cell.label.textColor = [UIColor blueColor];
NSString *name = self.ingredientsBook.names[indexPath.item];
[self.selectedIngredientNames addObject:name];
}
The problem is that when I tap the first cell it's not possible to select the 16th or 17th.
Or if I tap the first three ones it's not possible to select the three last ones.
The didSelectItemAtIndexPath is not being called I suppose.
I feel that it has to be something really simple but I can't see it right now.
I tried to put NSLogsin shouldSelectItemAtIndexPath for understand if that method was called and the method is not being called at all. This happens when there's a distance of 16 cells between the selected one and the problematic one.
Here are other data source methods and isSelectedIndexPath:
-(BOOL)isSelectedIndexPath:(NSIndexPath *)indexPath{
for (NSIndexPath *test in selectedCellIndexPaths){
if (test == indexPath){
return YES;
}
}
return NO;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return [self.ingredientsBook.names count];
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}
-(BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"%#", indexPath);
return YES;
}
-(void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath{
self.searchButton.enabled = ([[collectionView indexPathsForSelectedItems] count] > 0);
ButtonCell *cell = (ButtonCell *)[collectionView cellForItemAtIndexPath:indexPath];
cell.backgroundColor = [UIColor blackColor];
cell.label.textColor = [UIColor whiteColor];
[selectedCellIndexPaths removeObject:indexPath];
NSString *name = self.ingredientsBook.names[indexPath.item];
[self.selectedIngredientNames removeObject:name];
}

I found two problems. The prepareForReuse method seemed to be screwing things up, so I just deleted it. The main problem though, was the way you were implementing isSelectedIndexPath:. As soon as it finds the first selected item as you loop through the items, it returns YES and exits the loop. What you want to do, is just check if the indexPath is contained in the selectedCellIndexPaths array:
-(BOOL)isSelectedIndexPath:(NSIndexPath *)indexPath{
if ([selectedCellIndexPaths containsObject:indexPath]) {
return YES;
}else{
return NO;
}
}
Or, if you prefer to use a more succinct syntax, you can replace the if-else block with:
return ([selectedCellIndexPaths containsObject:indexPath])? YES : NO;

I recently faced the exact same issue with my app. UICollectionViewCell selection worked properly prior to iOS 8.3, subsequently I started to see some strange behaviour. Cells that were not actually selected would appear selected, other cells, seemingly at random could not be selected.
I had both custom setSelected and prepareForResuse methods implemented on a UICollectionViewCell subclass as such:
-(void)setSelected:(BOOL)selected
{
[super setSelected:selected];
if (selected)
{
[[self selectedIndicator] setHidden:NO];
}
else
{
[[self selectedIndicator] setHidden:YES];
}
}
-(void)prepareForReuse
{
[[self imageView] setImage:nil];
}
The prepareForReuse method simply reset an image view in the custom cell.
In my prepareForReuse method I did not make a call to [super prepareForReuse] (which according to the documentation does nothing by default). When I added the call to [super prepareForReuse] all selection worked as intended. Although Apple states the default implementation does nothing, they also recommend that super should be called. Following this recommendation solved my issue.

In iOS 10 I found that programmatically clearing a UICollectionView that had multiple-selection enabled was buggy when prefetching was enabled. And of course prefetch is on by default in iOS 10.

Related

collectionviewcell didSelectItemAtIndexPath/didDeselectItemAtIndexPath not responding for specific cells

I am trying to preselect some of the cell according to condition, which sets those cells selected and also change its background color while drawing those cells. Now the method
didSelectItemAtIndexPath / didDeselectItemAtIndexPath
is not getting called only for those preselected cells and hence I am not able to toggle selection and background color. The select/deselect delegate methods are being called for other cells
-(UICollectionViewCell*) collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"cellForItemAtIndexPath: %#", indexPath);
CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"collectionViewCell" forIndexPath:indexPath];
if(indexPath.row != 0 && indexPath.row != 8 && indexPath.section != 0 && indexPath.section != 25){
NSMutableDictionary *blockedHours = [blockedDaysArray objectAtIndex:indexPath.row-1];
NSString *blockedVal = [blockedHours valueForKey:#(indexPath.section-1).stringValue];
[cell setBlockedVal:(NSString*)blockedVal];
}
[cell addDayTimeLable:indexPath];
return cell;
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"didSelectItemAtIndexPath: %# ", indexPath);
NSMutableDictionary *blockedHours = [blockedDaysArray objectAtIndex:indexPath.row-1];
[blockedHours setValue:#"1" forKey:#(indexPath.section-1).stringValue];
CollectionViewCell *cell = (CollectionViewCell*)[collectionView cellForItemAtIndexPath:indexPath];
cell.selected = YES;
}
-(void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"didDeselectItemAtIndexPath %#", indexPath);
NSMutableDictionary *blockedHours = [blockedDaysArray objectAtIndex:indexPath.row-1];
[blockedHours setValue:#"0" forKey:#(indexPath.section-1).stringValue];
CollectionViewCell *cell = (CollectionViewCell*)[collectionView cellForItemAtIndexPath:indexPath];
cell.selected = NO;
}
In CollectionViewCell.m:
Self.selected calls the setter method and hence chagnes the background color
-(void)setBlockedVal:(NSString*)blockedVal{
if([blockedVal isEqualToString:#"1"]){
self.selected = YES;
}
}
-(void)setSelected:(BOOL)selected{
NSLog(#"set selected: %d", selected);
[super setSelected:selected];
if(selected)
self.backgroundColor = [SFIColors lightGreenColor];
else
self.backgroundColor = [UIColor whiteColor];
}
Note:
(1) didHighlightItemAtIndexPath/didUnHighlightItemAtIndexPath are
getting called for preselected cells.
(2)I Just found out that setting selected via didselect/didunselect is
redundant and I just removed from my code.Noticed that setSeleted is
auto called on clicking the other cells. Still this setSelected is not
being for preselected cells
Any Inputs to fix this or another way that I can do my task would be of great help.
I found the answer in this link: UICollectionView - didDeselectItemAtIndexPath not called if cell is selected
I actually searched a lot actually, but only now I found this link.
I had let my collection view know about my selection and that did the trick:
[collectionView selectItemAtIndexPath:indexPath animated:NO scrollPosition:UICollectionViewScrollPositionNone];

Update not visible cells in UICollectionView

I need an expandable UICollectionView in my app - so I came across this project (https://github.com/apploft/APLExpandableCollectionView). It's a subclass of UICollectionView that implements the expand and collapse behaviour.
I have extended the demo app to display a + or a - button in the expandable cells.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
APLCollectionViewCell* cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"APLCollectionViewCell" forIndexPath:indexPath];
if (indexPath.item == 0) {
cell.label.text = [NSString stringWithFormat:#"Section %li", (long)indexPath.section + 1];
cell.backgroundColor = [UIColor colorWithRed:58./255. green:165./255. blue:192./255. alpha:1.];
cell.indentView.hidden = YES;
cell.label_OnOff.text = #"+";
} else {
cell.label.text = [NSString stringWithFormat:#"Item %li", (long)indexPath.row];
cell.backgroundColor = [UIColor colorWithRed:58./255. green:165./255. blue:192./255. alpha:.5];
cell.indentView.hidden = NO;
[cell.label_OnOff setHidden:YES];
}
return cell; 
}
To switch between + and - I implemented the delegate methods:
- (void)collectionView:(UICollectionView *)collectionView didCollapseItemAtIndexPath:(NSIndexPath *)indexPath
{
APLCollectionViewCell *cell = (APLCollectionViewCell*)[collectionView cellForItemAtIndexPath:indexPath];
cell.label_OnOff.text = #"+";
}
- (void)collectionView:(UICollectionView *)collectionView didExpandItemAtIndexPath:(NSIndexPath *)indexPath
{
APLCollectionViewCell *cell = (APLCollectionViewCell*)[collectionView cellForItemAtIndexPath:indexPath];
cell.label_OnOff.text = #"-";
}
As you can see in the screenshot, the visible cells are updated correctly. Once I scroll down to the former invisible cells, the + button disappears.
This problem doesn't occur when there are only a few items in the UICollectionView, so that there is no need to scroll to further items.
Am I doing something wrong with IndexPath for invisble cells, or do you have any other hints for me?
Thank you!
Add the below line
cell.indentView.hidden = NO;
Just before your if else condition in cellForItemAtIndexPath. it may help you.
Thx Bhanu,
your answer pointed me to the right direction. The cells in the collectionview are REUSED, so I had to set cell.label_OnOff.hidden = NO; and cell.label_OnOff.hidden = YES; when I check for the IndexPath.

UICollectionView allowsMultipleSelelections not working

I am trying to make some photoPicker with CollectionView.
Have
allowsMultipleSelection = YES
Using following method
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
selectedPictures = [NSMutableArray array];
[selectedPictures addObject:[imagesArray objectAtIndex:indexPath.item]];
NSLog(#"Selected list:\n %#", selectedPictures);
NSLog(#"Objects in Array %i", selectedPictures.count);
}
While I am selecting cells, it's always adding to MutableArray only one object according it's indexPath. What could be an issue?
Why don't u keep the selectedPictures as a member variable
in your code
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
selectedPictures = [NSMutableArray array]; //keep on creation the new array on each selection
[selectedPictures addObject:[imagesArray objectAtIndex:indexPath.item]]; //adding the selected images means single image
NSLog(#"Selected list:\n %#", selectedPictures);
NSLog(#"Objects in Array %i", selectedPictures.count);
}
try this
put his in viewDidLoad
- (void)viewDidLoad
{
selectedPictures = [[NSMutableArray alloc]init]; //initilise hear
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
// selectedPictures = [NSMutableArray array]; //keep on creation the new array on each selection
[selectedPictures addObject:[imagesArray objectAtIndex:indexPath.item]]; //adding the selected images means single image to already initialised array
NSLog(#"Selected list:\n %#", selectedPictures);
NSLog(#"Objects in Array %i", selectedPictures.count);
}
Hope this helps u .. :)
it may be caused by not calling super. While the documentation for UICollectionReusableView fails to mention this, the documentation for UITableViewCell, which has the same method, does.
- (void)prepareForReuse
{
[super prepareForReuse]
// Your code here.
}
Old Answer:
This may be a bug with the UICollectionView.
What's happening is cells that were previously selected are being reused and maintain the selected state. The collection view isn't setting selected to "NO".
The solution is to reset the the selected state in prepareForReuse of the cell:
- (void)prepareForReuse
{
self.selected = NO;
}
If the reused cell is selected, the collection view will set selected to "YES" after prepareForReuse is called.
This is something the UICollectionView should be doing on it's own. Thankfully the solution is simple. Unfortunately I spent a ton of time working around this bug by tracking my own select state. I didn't realize why it was happening until I was working on another project with smaller cells.
Also Try this
I'm not seeing why this would take place. I do not believe the issue is the use of row vs item, though you really should use item. I can imagine, though, if your collection view has more than one section, that only looking at row/item but ignoring section would be a problem (i.e. it would select the same item number in every section).
To cut the Gordian knot, I'd suggest saving the NSIndexPath of the selected item, and then using that for the basis of comparison. That also makes it easy to render an optimization in didSelectItemAtIndexPath. Anyway, first define your property:
#property (nonatomic, strong) NSIndexPath *selectedItemIndexPath;
And then implement cellForItemAtIndexPath and didSelectItemAtIndexPath:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
CollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
cell.imageView.image = ...
if (self.selectedItemIndexPath != nil && [indexPath compare:self.selectedItemIndexPath] == NSOrderedSame) {
cell.imageView.layer.borderColor = [[UIColor redColor] CGColor];
cell.imageView.layer.borderWidth = 4.0;
} else {
cell.imageView.layer.borderColor = nil;
cell.imageView.layer.borderWidth = 0.0;
}
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
// always reload the selected cell, so we will add the border to that cell
NSMutableArray *indexPaths = [NSMutableArray arrayWithObject:indexPath];
if (self.selectedItemIndexPath)
{
// if we had a previously selected cell
if ([indexPath compare:self.selectedItemIndexPath] == NSOrderedSame)
{
// if it's the same as the one we just tapped on, then we're unselecting it
self.selectedItemIndexPath = nil;
}
else
{
// if it's different, then add that old one to our list of cells to reload, and
// save the currently selected indexPath
[indexPaths addObject:self.selectedItemIndexPath];
self.selectedItemIndexPath = indexPath;
}
}
else
{
// else, we didn't have previously selected cell, so we only need to save this indexPath for future reference
self.selectedItemIndexPath = indexPath;
}
// and now only reload only the cells that need updating
[collectionView reloadItemsAtIndexPaths:indexPaths];
}
Check also this
Your observation is correct. This behavior is happening due to the reuse of cells. But you dont have to do any thing with the prepareForReuse. Instead do your check in cellForItem and set the properties accordingly. Some thing like..
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cvCell" forIndexPath:indexPath];
if (cell.selected) {
cell.backgroundColor = [UIColor blueColor]; // highlight selection
}
else
{
cell.backgroundColor = [UIColor redColor]; // Default color
}
return cell;
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *datasetCell =[collectionView cellForItemAtIndexPath:indexPath];
datasetCell.backgroundColor = [UIColor blueColor]; // highlight selection
}
-(void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *datasetCell =[collectionView cellForItemAtIndexPath:indexPath];
datasetCell.backgroundColor = [UIColor redColor]; // Default color
}
I solved my issue;
The problem was very simple, I should have initialise MutableArray not in the Method didSelectItemAtIndexPath, but in the ViewDidLoad. Now it adding pictures one by one

Using 2 UICollectionView instances but cells aren't showing in one of them

Goal:
Set up two collectionViews with different styles. One grid style and other single file style. These collectionViews will be toggleable giving users shopping within the ability to view the items for sale in which ever style (grid/single file) they prefer.
I have an existing collectionView with a custom cell set up in interface builder and it's working fine. I've tried to find info on how to add a second one via interface builder but have had no luck in finding any.
What I've done:
I've created the second collectionView programatically in my viewDidLoad method. I have an instance variable named _collectionView2.
- (void)viewDidLoad
{
[super viewDidLoad];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
_collectionView2 = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:layout];
[_collectionView2 setDataSource:self];
[_collectionView2 setDelegate:self];
[_collectionView2 registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"Cell"];
[_collectionView2 setBackgroundColor:[UIColor redColor]];
[self.view addSubview:_collectionView2];
[_collectionView2 setHidden:YES];
I've modified delegate and datasource methods to make them aware about the new collectionView:
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
if (collectionView == _collectionView) {
NSArray *people = [_thisController objects];
return [people count];
} else {
return 20;
}
}
.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
{
if (collectionView == _collectionView) {
static NSString *CellIdentifier = #"Cell";
VAGGarmentCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: CellIdentifier forIndexPath:indexPath];
[[cell activityIndicator] startAnimating];
PFFile *userImageFile = [object valueForKey:#"image"];
[[cell imageView] setFile: userImageFile];
[[cell imageView] loadInBackground];
[[cell activityIndicator] stopAnimating];
[[cell title] setText:[object valueForKey:#"title"]];
[[cell price] setText:[NSString stringWithFormat: #"£%# GBP", [object valueForKey:#"price"]]];
return cell;
} else {
static NSString *CellIdentifier = #"Cell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
cell.backgroundColor = [UIColor greenColor];
return cell;
}
//_addToFavouritesButton = [cell addFavouriteButton];
[_addToFavouritesButton addTarget:_thisController action:#selector(addToFavouritesButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
}
.
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
if (collectionView == _collectionView2) {
return CGSizeMake(50, 50);
} else {
return CGSizeMake(140, 272);
}
}
Then I have a method that responds to the UISegmentControl being switched from grid style display to single file display:
- (void)displayTypeSegmentSelected
{
_selectedDisplayTypeIndex = [_displayTypeControl selectedSegmentIndex];
if (_selectedDisplayTypeIndex == 0) {
NSLog(#"Single file item view selected");
[_collectionView setHidden:YES];
[_collectionView2 setHidden:NO];
} else {
NSLog(#"Grid style view selected");
[_collectionView setHidden:NO];
[_collectionView2 setHidden:YES];
}
}
CollectionView2 is hidden initially then unhidden when the segment control is used. It looks like it's working because when I toggle a red background shows and I did set a red background when I created the collectionView but green cells aren't showing at all.
I can't seem to see where I've gone wrong. I'd like to get this working so I can replace the cell with my custom cell. The only difference between grid display and single file display is that single file display will be an enlarge cell that takes up the width of the view. All properties etc will be the exact same meaning I can use my current custom cell.
Why aren't my cells showing? I've followed a clear tutorial on creating a collectionView programmatically but still no cells are showing.
Update:
I put a log message in the if statement that checks what collectionView is present and it is only ever trigger for my _collectionView and not _collectionView2.
Help is appreciated.
Regards
Not sure this will fix your issue, but when I have 2 collection views in one controller, I use their tag's to determine which one I am dealing with in a given function, like cellForItemAtIndexPath.
I also have always used different cell identifiers for the two cells and usually different subclasses of uicollectionviewcell.
Where do you create the first collection view and set its delegate?
Reloading the table data in the method my UISegmentedControl triggers got the cells to show up.

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.
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”.
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'.
Cells are being reused because of that they are refreshing.
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.

Resources