UICollectionViewCell data updating with every reload call - ios

I need cells in a UICollectionView which will either show text or an image.
I am implementing this by creating my own UICollectionViewCell having two properties - UIImage property and NSString property.
Depending on which property the caller sets, I show either the image or the text. The calling collectionview is also reusing the cells by using :
cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
But whenever I reload the collectionview in my code the cells get all messed up - they show empty buttons. What am I doing wrong? Let me know if you need me to post any other specific code piece. I didn't want to clutter my question with useless code.
In my CollectionViewCell, I am resetting both the label and the image:
-(void) prepareForReuse {
[super prepareForReuse];
[_button setTitle:#"" forState:UIControlStateNormal];
[_button setImage:nil forState:UIControlStateNormal];
[_button removeTarget:nil action:NULL forControlEvents:UIControlEventAllEvents];
}
cellForItemAt code:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
NSInteger position = indexPath.row;
long listIdx = position - 1;
if (listIdx == ADD_ICON_INDEX) {
UIImage * addImage = [self _getAddImage];
[cell setButtonImage:addImage target:self action:#selector(_addButtonClicked:)];
cell.button.tag = listIdx;
return cell;
}
NSString *title= _visibleButtons[listIdx];
[cell setButtonTitle:title target:self action:#selector(_buttonClicked:)];
cell.backgroundColor = [UIColor clearColor];
cell.button.tag = listIdx;
return cell;
}

Related

Single CollectionView for Multi Tab SegmentControl

I want to display two collection view using segment controller, on change of segment second collection view will display.
my code is :
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"morningcell" forIndexPath:indexPath];
UICollectionViewCell *cell1 = [collectionView dequeueReusableCellWithReuseIdentifier:#"eveningcell" forIndexPath:indexPath];
if (_segment.selectedSegmentIndex == 0) {
UILabel *lbl = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, cell.frame.size.height,cell.frame.size.width)];
lbl.text = [numbers objectAtIndex:indexPath.row];
[cell addSubview:lbl];
[lbl setTextAlignment:UITextAlignmentCenter];
return cell;
}
else{
UILabel *lbl = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, cell1.frame.size.height,cell1.frame.size.width)];
lbl.text = [numbers objectAtIndex:indexPath.row];
[cell1 addSubview:lbl];
[lbl setTextAlignment:UITextAlignmentCenter];
return cell1;
}}
Do not create two collectionView for the task that you want because you can achieve it using a single collectionView.
#interface YourVC(), UICollectionViewDelegate, UICollectionViewDataSource {
NSArray *arrayOfModelsForFirstCollectionView;
NSArray *arrayOfModelsForSecondCollectionView;
}
#end
#implementation YourVC
-(void)viewDidLoad{
[super viewDidLoad];
//Fetch Data For both Segments Using Your Web API Call or Local Database API Call
}
-(IBAction)didChangeSegmentIndex:(id)sender{
//When segment is changed you need to tell your collectionView to update the data.
[self.collectionView reloadData];
}
-(NSInteger) collectionView:(UICollectionView *)collectionView numberOfRowsInSection:(NSInteger)section{
//When reloadData is call this method will check that which index is selected.
//And according to selection it will create number of cells.
if (self.segmentControl.selectedIndex == 0){
return arrayOfModelsForFirstCollectionView.count;
}else{
return arrayOfModelsForSecondCollectionView.count;
}
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
//While creating a cell it will check which segment is selected so it will initialize and create the cell with respect to selection of segments.
//Hence you have created two different cells in a single collectionView toggling with a segmentControl.
if (_segment.selectedSegmentIndex == 0) {
return [self makeCellForFirstSegmentAtIndexPath: indexPath];
}
else{
return [self makeCellForSecondSegmentAtIndexPath: indexPath];
}
}
-(UITableViewCell *)makeCellForFirstSegmentAtIndexPath:(NSIndexPath *)indexPath{
//To show data from array you need to use arrayOfModelsForFirstCollectionView because this array is associated with First Segment Index
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"morningcell" forIndexPath:indexPath];
UILabel *lbl = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, cell.frame.size.height,cell.frame.size.width)];
lbl.text = [arrayOfModelsForFirstCollectionView objectAtIndex:indexPath.row];
[cell addSubview:lbl];
[lbl setTextAlignment:UITextAlignmentCenter];
return cell;
}
-(UITableViewCell *)makeCellForSecondSegmentAtIndexPath:(NSIndexPath *)indexPath{
//To show data from array you need to use arrayOfModelsForSecondCollectionView because this array is associated with Second Segment Index
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"eveningcell" forIndexPath:indexPath];
UILabel *lbl = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, cell.frame.size.height,cell.frame.size.width)];
lbl.text = [arrayOfModelsForSecondCollectionView objectAtIndex:indexPath.row];
[cell addSubview:lbl];
[lbl setTextAlignment:UITextAlignmentCenter];
return cell;
}
#end

How to handle Custombutton CheckedAnduncheck functionality in collectionviewcell in objective c with multiselection

I want collectionview cell in which i placed custom button checkboxes used for adding member to the group.multiselection is also possible.but when i select particular checkbox alongwith it the other checkbox which is not visible is also selected which is at same indexpath from lastvisible cell below is my code
`
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
CGCell *cell = [_CGCol dequeueReusableCellWithReuseIdentifier:#"CELL" forIndexPath:indexPath];
[cell.btn addTarget:self action:#selector(CheckUncheckFunctionality:) forControlEvents:UIControlEventTouchUpInside];
cell.btn.tag = indexPath.row;
return cell;
}
-(void)CheckUncheckFunctionality:(id)sender
{
UIButton *btn = (UIButton *)sender;
if (btn.selected) {
[btn setImage:[UIImage imageNamed:#"unchacked.png"] forState:UIControlStateNormal];
}
else{
[btn setImage:[UIImage imageNamed:#"checked.png"] forState:UIControlStateNormal];
}
}
`
declare global Array and inititlize
NSMutableArray * ArrCheckIndexs=[[NSMutableArray alloc] init];
for (int i=0; i<no.ofcell; i++)
{
[ArrCheckIndexs addObject:#"0"];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CGCell *cell = [_CGCol dequeueReusableCellWithReuseIdentifier:#"CELL" forIndexPath:indexPath];
[cell.btn addTarget:self action:#selector(CheckUncheckFunctionality:) forControlEvents:UIControlEventTouchUpInside];
cell.btn.tag = indexPath.row;
return cell;
}
-(void)CheckUncheckFunctionality:(id)sender
{
UIButton *btn = (UIButton *)sender;
if ([[ArrCheckIndexs objectAtIndex:btn.tag] isEqualToString:#"1"])
{
[btn setImage:[UIImage imageNamed:#"unchacked.png"] forState:UIControlStateNormal];
[ArrCheckIndexs replaceObjectAtIndex:btn.tag withObject:#"0"];
}
else
{
[btn setImage:[UIImage imageNamed:#"checked.png"] forState:UIControlStateNormal];
[ArrCheckIndexs replaceObjectAtIndex:btn.tag withObject:#"1"];
}
}
The cell might be getting reused. And Since you are not updating the cells when they are returned by the cellForItemAtIndexPathmethod they are in their last updated state. Solution : -
I presume you are using models for cell information
Add an boolean flag in the model which will store whether the item is checked or not. Initially false
When you will return the cell check for the model for the flag value for the cells. and update the button state accordingly.

Changing Button Color in UITableviewCell

I am trying to change the color of a button when pressed that is in a tableviewCell. However my code changes the color of every button in the table and not just the one in the cell I selected,
How would I go about just changing the color of the button I pressed.
Please see my code below,
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UIButton *addNotesButton = (UIButton *)[cell viewWithTag:106];
[addNotesButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Test";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
UIButton *addNotesButton = (UIButton *)[cell viewWithTag:106];/
[addNotesButton addTarget:self action:#selector(addNotes :) forControlEvents:UIControlEventTouchUpInside];
}
The main issue might be in your cellForRowAtIndexPath: method. UITableView cells are re-used as they are displayed on the screen. dequeueReusableCellWithIdentifier: method returns a cell if it has been marked as ready for reuse. You must have seen this method of UITableView being used in cellForRowAtIndexPath: method. (See this link)
So in cellForRowAtIndexPath: you will have to configure each cell as it is being loaded or else it will display old values (since the cells are being reused).
You can either declare a property or a simple variable of type NSIndexPath.Let the variable be called _selectedIndexPath. Then in didSelectRowAtIndexPath: you can assign this property to the indexPath selected.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSArray *indexPaths = nil;
if (_selectedIndexPath) {
indexPaths = #[_selectedIndexPath, indexPath];
} else {
indexPaths = #[indexPath];
}
_selectedIndexPath = indexPath;
[tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
}
- (void)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Your Cell Identifier"];
UIButton *addNotesButton = (UIButton *)[cell viewWithTag:106];
if (indexPath.row == _selectedIndexPath.row) {
[addNotesButton setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
} else {
[addNotesButton setTitleColor:[UIColor clearColor] forState:UIControlStateNormal];
}
}
You don't need to change color manually in did select row at index path. Just set the color for UIControlStateSelected and on the action of button tap set the buttons selected property to YES. From your code i think this should work.
inside cell for row at index path method
[addNotesButton setTitleColor:[UIColor blueColor] forState:UIControlStateSelected];
and in button action method
-(IBAction)addNotes:(id)sender
{
UIButton *button = (UIButton*)sender;
buttons.selected = !button.isSelected;
}
I think this will work.
After trying everything and failed. I ended up having a hidden value in each row that would change when the button is pressed. So the code reads the value then configures the button for each row.

UICollectionView adding button to cell

I'm adding a button to a cell of a collection view as below
- (void)activateDeletionMode:(UILongPressGestureRecognizer *)gr
{
if (gr.state == UIGestureRecognizerStateBegan)
{
NSLog(#"deletion mode");
if(self.isDeleteActive == NO){
self.isDeleteActive = YES;
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:[gr locationInView:self.collectionView]];
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
self.deletedIndexpath = indexPath.row;
self.deleteButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self.deleteButton addTarget:self
action:#selector(deleteImage:)
forControlEvents:UIControlEventTouchUpInside];
[self.deleteButton setBackgroundImage: [UIImage imageNamed:#"delete.png"] forState:UIControlStateNormal];
self.deleteButton.frame = CGRectMake(10, 0, 10, 10);
[cell addSubview:self.deleteButton];
}
}
}
The problem is, when the cell is reused when the collection view is scrolled, I see the button displayed in this cell too. How do I avoid this happening? Code for collection view below:
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath;
{
Cell *cell = [cv dequeueReusableCellWithReuseIdentifier:kCellID forIndexPath:indexPath];
cell.image.image = [self.imageArray objectAtIndex:indexPath.row];
return cell;
}
In the past I've done things like this, when adding the view add a tag:
self.deleteButton.frame = CGRectMake(10, 0, 10, 10);
//Mark the view with a tag so we can grab it later
self.deleteButton.tag = DELETE_BUTTON_TAG;
[cell addSubview:self.deleteButton];
Then remove it from any new recycled cells:
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath;
{
Cell *cell = [cv dequeueReusableCellWithReuseIdentifier:kCellID forIndexPath:indexPath];
//Remove the delete view if it exists
[[cell viewWithTag:DELETE_BUTTON_TAG] removeFromSuperview];
cell.image.image = [self.imageArray objectAtIndex:indexPath.row];
return cell;
}

UICollectionView reload data Issue

I have a problem with data reloading using UICollectionView. I have this array on the viewDidLoad to fill the UICollectionView.
array = [[NSMutableArray alloc] init];
[array addObject:#"1"];
[array addObject:#"2"];
[array addObject:#"3"];
[array addObject:#"4"];
[array addObject:#"5"];
[array addObject:#"6"];
and the method:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier = #"Cell";
//cell = [[UICollectionViewCell alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
myCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(5, 0, 100, 20)];
[titleLabel setText:[array objectAtIndex:indexPath.row]];
titleLabel.textColor = [UIColor whiteColor];
titleLabel.backgroundColor = [UIColor clearColor];
[cell addSubview:titleLabel];
return cell;
}
I tap the UIButton and data is reloaded:
[myCollectionView reloadData];
Here how the data looks like before and after the reload:
You add a new label each time you tap the reload button. You should add the label once and change the label text according.
Here a simple example.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = (MyCell *)[collectionView dequeueReusableCellWithReuseIdentifier:#"Cell"
forIndexPath:indexPath];
[cell setMyTextLabel:indexPath.row];
return cell;
}
where MyCell will contains a UILabel and a property to modify its text.
I really suggest to take a look at Fun with UICollectionView code by #Ben Scheirman.
Hope that helps.
P.S. Rename myCell to MyCell. A class should start with an uppercase letter.
You are adding your label when you tap reload button. In that case you are adding label again and again...
So there are three solutions:
Make your cell reusable
Remove your cell from superview in reload method.
You can check if label's text length is not equal to zero. In that case no need to add that label and change text.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:#"cellIdentifier" forIndexPath:indexPath];
dispatch_async(dispatch_get_main_queue(), ^{
// ********* Changed *******
for (UIView *v in [cell.contentView subviews])
[v removeFromSuperview];
// ********** Changed **********
if ([self.collectionviewFlow.indexPathsForVisibleItems containsObject:indexPath]) {
NSString *img_name=[NSString stringWithFormat:#"%#_thumb%d.png",self.VaritiesName,(int)indexPath.row+1];
imageVw=[[UIImageView alloc]initWithImage:[UIImage imageNamed: img_name]];
imageVw.frame=CGRectMake(10,10,100,100);
[cell.contentView addSubview:imageVw];
}
});
cell.backgroundColor=[UIColor clearColor];
return cell;
}
It is quit late, but here is my solution.
if you used custom collectionviewcell
try to use 'func prepareForReuse()'

Resources