UICollectionViewCell Selection selects/highlights every 5 cells - ios

I have a horizontal collectionview with code to highlight/color the cell selected. It highlights the cell that was selected, but then every 5 cells after also get highlighted. Any idea what is going on?
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
for(int x = 0; x < [cellArray count]; x++){
UICollectionViewCell *UnSelectedCell = [cellArray objectAtIndex:x];
UnSelectedCell.backgroundColor = [UIColor colorWithRed:0.2 green:0.5 blue:0.8 alpha:0.0];
}
UICollectionViewCell *SelectedCell = [cellArray objectAtIndex:indexPath.row];
SelectedCell.backgroundColor = [UIColor colorWithRed:0.2 green:0.5 blue:0.8 alpha:1.0];
cellSelected = indexPath.row;
NSLog(#"%i", cellSelected);
}

That happens because cells are reused when you scroll. You have to store the "highlighted" status for all rows in your model (for example in an array or NSMutableIndexSet), and in collectionView:cellForItemAtIndexPath: set the background color of the cell according to the status for that row.
In didSelectItemAtIndexPath it should be sufficient to set the color of the newly selected
and the previously selected cell.
Update: If only one cell can be selected at a time, you just have to remember the
index path of the selected cell.
Declare a property selectedIndexPath for the currently highlighted row:
#property (strong, nonatomic) NSIndexPath *selectedIndexPath;
In didSelectItemAtIndexPath, unhighlight the previous cell, and highlight the new cell:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (self.selectedIndexPath != nil) {
// deselect previously selected cell
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:self.selectedIndexPath];
if (cell != nil) {
// set default color for cell
}
}
// Select newly selected cell:
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
if (cell != nil) {
// set highlight color for cell
}
// Remember selection:
self.selectedIndexPath = indexPath;
}
In cellForItemAtIndexPath, use the correct background color:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Identifier" forIndexPath:indexPath];
if ([self.selectedIndexPath isEqual:indexPath) {
// set highlight color
} else {
// set default color
}
}

I think you use reusable cell for collection - there is the point.
You shell to set default background color before reuse cell.

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];

Uicollectionview chess style grid

I have to implement a chess style grid view to show some radial progress bar.!
I made the grid using some inefficient algorithm to do this, since UiCollectionView don't use rows and columns, and it worked, but when the user scrolled the collection to the limit and the "row" of the collection disappear from the screen and the user release the screen and the UICollectionViewCells reappear they switched its colors.
I know the switching happens by the BOOLs, but i don't know how solve this.
This is my algorithm (i have 2 differents UICollectionViewCells, one for each color)
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"Cell";
static NSString *identifierPair = #"CellPair";
UICollectionViewCell *cell ;
UILabel *title;
//...Creating the circle progress bar....
if ((indexPath.item % 2) == 0) {
if (isPair) {
cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifierPair forIndexPath:indexPath];
isPair = NO;
title = (UILabel *)[cell viewWithTag:12];
}
else
{
cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
isPair = YES;
title = (UILabel *)[cell viewWithTag:11];
}
}
else {
if (isUneven) {
cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifierPair forIndexPath:indexPath];
isUneven = NO;
title = (UILabel *)[cell viewWithTag:12];
}
else
{
cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
isUneven = YES;
title = (UILabel *)[cell viewWithTag:11];
}
}
//...Setting the name to the cell....
return cell;
}
This seems more the case of using a single checkered background inside the UICollectionView, but since I'm not sure that it is doable you could do something like:
+ (BOOL)checkersCellAtIndexIsDark:(NSIndexPath *)indexPath {
NSInteger squareIndex = indexPath.item % 4;
return (squareIndex == 0 || squareIndex == 3);
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
cell.backgroundColor = ([[self class] checkersCellAtIndexIsDark:indexPath])? [UIColor orangeColor] : [UIColor yellowColor];
// Set other things
return cell;
}
I don't understand why you use different reuseIdentifiers and titleLabels for different backgrounds since I don't notice from the screens, but you can always edit this code
You didn't have to use 2 different cells to change the background.
All you need is to set the default color of the background and when the indexPath.item is even, just set the other color for the cell background.
But is important to set the default color first, otherwise you will have the same result.

Reusing cell causes unwanted behaviour when updating cell view

I want ot set a border to a selected cell, i save a cell property that represents the selected one and manipulate it:
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
NSArray *eyeArray = [self eyesArrayConfigure][indexPath.row];
if (indexPath.row==5) {
int r = arc4random() % 6;
eyeArray = [self eyesArrayConfigure][r];
}
[borderedCell.contentView.layer setBorderWidth:0.0f] ;
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
borderedCell = cell;
[borderedCell.contentView.layer setBorderColor:self.view.tintColor.CGColor];
[borderedCell.contentView.layer setBorderWidth:3.0f];
}
and the cellForView: (Im usinng 2 types of cell identifiers because one cell contains a label - "Random cell":
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row ==5) {
UICollectionViewCell *randomCell =[collectionView
dequeueReusableCellWithReuseIdentifier:#"randomCell"
forIndexPath:indexPath];
randomCell.backgroundColor = [UIColor purpleColor];
borderedCell = randomCell;
[borderedCell.contentView.layer setBorderColor:self.view.tintColor.CGColor];
[borderedCell.contentView.layer setBorderWidth:3.0f];
return randomCell;
}
UICollectionViewCell *myCell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
NSArray *eyeArray = [self eyesArrayConfigure][indexPath.row];
myCell.backgroundView = [[UIImageView alloc] initWithImage:eyeArray[1]];
return myCell;
}
What i get is if a click one cell it will be fine till i scroll and then i get weird behaviour when couple of cells might be with the border.
Thanks for the help.
The solution is to use a view model. You are already doing this for your cells' background views. Apply the same concept for the border.
#interface MyCollectionView ()
#property (nonatomic) NSInteger selectedRow;
#end
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
//update self.selectedRow and either reload entire collection view or just the currently selected and previously selected.
self.selectedRow = indexPath.row;
[collectionView reloadData];
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *myCell = [collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
CGFloat borderWidth = (indexPath.row == self.selectedRow) ? 3.0 : 0.0;
[myCell.contentView.layer setBorderWidth:borderWidth];
return myCell;
}

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

UICollectionView - Image is getting set randomly

I am using collectionView in my App. I am setting image for the cell backgroundView in didSelect delegate. But When i select one cell indexPath the image is getting set for 3 cell indexPath. When i scroll the collectionView the images are getting changed randomly? Please Help me. thanks in advance.
- (void)viewDidLoad
{
[super viewDidLoad];
[collection registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:uio];
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection: (NSInteger)section
{
return 50;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collection dequeueReusableCellWithReuseIdentifier:uio
forIndexPath:indexPath];
cell.backgroundColor = [UIColor whiteColor];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"index %#",indexPath);
UICollectionViewCell *cell = [collection cellForItemAtIndexPath:indexPath];
cell.backgroundView =[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"download.jpg"]];
}
That's because you reuse your cell. An option would be to have an dictionary variable to say that your cell has been selected and reset the image if it has not been.
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"index %#",indexPath);
UICollectionViewCell *cell = [collection cellForItemAtIndexPath:indexPath];
cell.backgroundView =[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"download.jpg"]];
[selectedDictionary setObject:[NSNumber numberWithBool:YES] forKey:[NSNumber numberWithInteger:indexPath.row]];
}
Then in your cellForItemAtIndexPath method you would check that value
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collection dequeueReusableCellWithReuseIdentifier:uio
forIndexPath:indexPath];
BOOL selected = [[selectedDictionary objectForKey:[NSNumber numberWithInteger:indexPath.row]] boolValue];
if(selected){
cell.backgroundView =[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"download.jpg"]];
}else{
cell.backgroundView = nil;
}
cell.backgroundColor = [UIColor whiteColor];
return cell;
}
Of course if you use some kind of object as model, it would appropriate to have a selected variable in here, you won't need a nsdictionary any more.
The Problem is dequeueReusableCellWithReuseIdentifier.
When you scroll UICollectionview then cell are reused that is problem
add Collectionview inside scrollview.
Try this Inside:
Scroll_View is Your Scroll View
collection is Your Collectionview
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
self.Scroll_View.contentSize = CGSizeMake(self.view.frame.size.width, collectionView.contentSize.height);
CGRect fram_For_Collection_View = self.collection_view.frame;
fram_For_Collection_View.size.height = collectionView.contentSize.height;
self.collection.view.frame = fram_For_Collection_View;
}
Your -collectionView:didSelectItemAtPath: is adding a new image view to the cell. Nothing is removing that image view when the cell is reused. So, when you say:
UICollectionViewCell *cell = [collection dequeueReusableCellWithReuseIdentifier:uio
forIndexPath:indexPath];
in your -collectionView:cellForItemAtIndexPath:, you're may get back some cell that already has one or more image views.
My suggestion would be to add the image view to the cell in the cell prototype, perhaps in your storyboard or in the cell's initializer. Have your -collectionView:cellForItemAtIndexPath: set the image for that image view to the correct image for the given path.
What's happening is that UICollectionView reuses cells. So in didSelectItemAtIndexPath: you set the cell background, but then the UICollectionView reuses that same cell as needed (and you're not resetting the cell.backgroundView in cellForItemAtIndexPath:).
The way to fix this is to maintain an NSIndexSet of selected cells. In didSelectItemAtIndexPath: you can add the index of the item that was selected, and then force a reload of that item by calling reloadItemsAtIndexPaths. Then, in your cellForItemAtIndexPath: check the index set to see if the selected index is included, and if so, set the backgroundView of the cell.
I had the same issue few days ago & I posted a question here. Here is the answer I got & it works for me.
Collection View Cell multiple item select Error
And also if you are using a custom cell you can add this code to the init method of that cell & it will work too.
CGFloat borderWidth = 6.0f;
UIView *bgView = [[UIView alloc] initWithFrame:frame];
bgView.layer.borderColor = [UIColor redColor].CGColor;
bgView.layer.borderWidth = borderWidth;
self.selectedBackgroundView = bgView;

Resources