Custom UITableViewCell weird behavior with fast scroll - ios

I have a grouped tableView with 5 sections. The tableView uses a custom UITableViewCell with 2 label and 4 buttons in it. When i select 1 or more buttons at the beginning of the table and then scroll to the end of it, i find those buttons selected in the last cell, sometimes in the second-last. It seems to me that there is some issues with the dequeueReusableCellWithIdentifier but i cannot figure it out.
For clarity i have this code in my viewDidLoad:
// table cell
UINib *nib = [UINib nibWithNibName:#"SMRateMeetingTableViewCell" bundle:nil];
[self.tableView registerNib:nib forCellReuseIdentifier:#"SMRateMeetingTableViewCell"];
and this in my cellForRowAtIndexPath:
SMRateMeetingTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"SMRateMeetingTableViewCell" forIndexPath:indexPath];
// Configure the cell...
if (cell == nil) {
cell = [[SMRateMeetingTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"SMRateMeetingTableViewCell"];
}
Pretty basic stuff.
I added some screens for better understanding.
EDIT: adding buttons code.
For an easier analysis let's assume i only have 1 button in the custom cell
This is the table view code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
SMRateMeetingTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"SMRateMeetingTableViewCell" forIndexPath:indexPath];
// Configure the cell...
if (cell == nil) {
cell = [[SMRateMeetingTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"SMRateMeetingTableViewCell"];
}
//the tag will allow me to understand in which section is the button
cell.firstYesButton.tag = indexPath.section;
[cell.firstYesButton addTarget:self action:#selector(firstYesButtonAction:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
And the relative method associated to the button:
-(IBAction)firstYesButtonAction:(id)sender
{
UIButton *senderButton = (UIButton *)sender;
[self.votesArray replaceObjectAtIndex:(senderButton.tag*2) withObject:[NSNumber numberWithInt:1]];
//NSLog(#"%#", self.votesArray );
}
An this is the code in the implementation file of the custom cell:
#implementation SMRateMeetingTableViewCell
- (void)awakeFromNib {
// Initialization code
[self.firstYesButton setImage:[UIImage imageNamed:#"yesRateDisable.png"] forState:UIControlStateNormal];
}
- (IBAction)firstYesAction:(id)sender {
[self.firstYesButton setImage:[UIImage imageNamed:#"yesRateEnable.png"] forState:UIControlStateNormal];
}

This is because the table view is recycling your cell. This is what the dequeueReusableCellWithIdentifier: method is doing. Because it is recycling views instead of a new one always being created, you will have to set certain properties, such as the selected state of your buttons, otherwise they will retain the properties that they had when they were enqueued.

As UITableViewCell's get recycled which is quite unpredictable.
So one approach is to maintain the state with key value pairs
that this NSDictionary and set images as per state changed in NSDictionary like
#{
"0":"EnableImage",
"1":"DisableImage",
"2":"DisableImage",
"3":"EnableImage",
}
so set image as per the above dictionary in cellForRowAtIndexPath.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
.... ....
You need set image as per state maintained in above dictionary
return cell;
}
And Remove below code
(void)awakeFromNib {
// Initialization code
[self.firstYesButton setImage:[UIImage imageNamed:#"yesRateDisable.png"] forState:UIControlStateNormal];
}
- (IBAction)firstYesAction:(id)sender {
[self.firstYesButton setImage:[UIImage imageNamed:#"yesRateEnable.png"] forState:UIControlStateNormal];
}

Related

Table view scrolling / dequeueReusableCell issue

I have a table view & a custom cell. the cell contains 3 buttons (check box type) . on button click the respective buttons text i need to change (check / uncheck).
I achieved this, but when i click 1st button on top cell and scroll down the new cell at the bottom also has this check mark, and when i scroll back to top the check mark is moved to next cell.. how to fix this??
code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *strCellIdentifier = #"RemoteCell";
RemoteCustomCell *cell = (RemoteCustomCell*)[tableView ![dequeueReusableCell][2]WithIdentifier:strCellIdentifier];
if (cell == nil) {
cell = [[RemoteCustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:strCellIdentifier];
}
else {
cell = [cell initWithStyle:UITableViewCellStyleDefault reuseIdentifier:strCellIdentifier];
}
[cell.btnCheck1 addTarget:self action:#selector(CheckButton1_Click:) forControlEvents:UIControlEventTouchUpInside];
}
return cell;
}
- (void)CheckButton1_Click:(UIButton*)sender
{
RemoteControllCustomCell *clickedCell = (RemoteControllCustomCell *)[[sender superview] superview];
if(clickedCell.btnCheck1.selected)
{
[clickedCell.btnCheck1 setTitle:#"O" forState:UIControlStateNormal];
clickedCell.btnCheck1.selected = NO;
}
else
{
[clickedCell.btnCheck1 setTitle:#"X" forState:UIControlStateSelected];
clickedCell.btnCheck1.selected = YES;
}
}
screenshot:
In your RemoteCustomCell.m file you should implement
- (void)prepareForReuse
{
[super prepareForReuse];
cell.btnCheck1.selected = NO;
}
This way every cell that is reused will have it's btnCheck1.selected value set to NO, and when you load your cell in cellForRowAtIndexPath it will only set it to YES when the cell comes visible and you set it to that.
But it is key to store all your values in an NSMutableArray. There is no such thing as storing your values in the cells only, they get reused on a basis that can not be foreseen. Add your values to the array and use [myArray objectAtIndex:indexPath.row]; to open those values in a cell.
An example:
Somewhere in viewDidLoad
NSMutableArray *myArray = [[NSMutableArray alloc] initWithObjects:#"1", #"0", #"1", #"1", nil];
In your cellForRowAtIndexPath
BOOL yesOrNo = [[myArray objectAtIndex:indexPath.row] boolValue];
And then set your button.selected to the boolean.
This is a typical issue, where you are relying on the UI to the job of your model. The model, the thing that you should pass to your UITableViewCell, so it can be built, would tell it, if it should be displaying an "X" or an "O". Since you are not doing this, the easiest solution, would be to simply reset the state of the cell everytime it gets dequeued.
I think you need to store the state in a array and check the state in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
CheckButton1_Click do change the state, but when dequeueReusableCell , it loads from cellForRowAtIndexPath again.
It seems like you have dequeue of cell issue. You may implement cellForRowAtIndexPath method as below.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"RemoteCell";
RemoteCustomCell *cell = (RemoteCustomCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell==nil) {
NSArray *arrNib=[[NSBundle mainBundle] loadNibNamed:CellIdentifier owner:self options:nil];
cell= (RemoteCustomCell *)[arrNib objectAtIndex:0];
}
[cell.btnCheck1 addTarget:self action:#selector(CheckButton1_Click:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}

Having problems with the method: prepareForReuse

I have a custom UITableViewCell, and when it's selected, it expands and adds a UILabel to the selected cells UIView that I added in the storyBoard.
When I run the app and select a cell, the label gets added to myView as expected. The problem is, when I scroll down, the label is also shown at another cell.
Apparently the reason its behaving like so, is because I'm reusing the cell and I don't clean them as Emilie stated. I'm trying to call the method of prepareForReuse and 'cleaning' the cell, but I'm having trouble doing that. Here is my code:
- (void)prepareForReuse {
NSArray *viewsToRemove = [self.view subviews];
for (UILablel *v in viewsToRemove) {
[v removeFromSuperview];
}
Doing that, cleans even the selected cells label.
- (void)viewDidLoad {
self.sortedDictionary = [[NSArray alloc] initWithObjects:#"Californa", #"Alabama", #"Chicago", #"Texas", #"Colorado", #"New York", #"Philly", #"Utah", #"Nevadah", #"Oregon", #"Pensilvainia", #"South Dekoda", #"North Dekoda", #"Iowa", #"Misouri", #"New Mexico", #"Arizona", #"etc", nil];
self.rowSelection = -1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CategorieCell *customCell = [tableView dequeueReusableCellWithIdentifier:#"cellID" forIndexPath:indexPath];
customCell.title.text = [self.sortedDictionary objectAtIndex:indexPath.row];
return customCell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
CategorieCell *customCell = (CategorieCell *)[tableView cellForRowAtIndexPath:indexPath];
if (self.info) {
[self.info removeFromSuperview];
}
self.info = [[UILabel alloc] init];
[self.info setText:#"Hello"];
[self.info setBackgroundColor:[UIColor brownColor]];
CGRect labelFrame = CGRectMake(0, 0, 50, 100);
[self.info setFrame:labelFrame];
[customCell.infoView addSubview:self.info];
NSLog(#"%ld", (long)indexPath.row);
self.rowSelection = [indexPath row];
[tableView beginUpdates];
[tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([indexPath row] == self.rowSelection) {
return 159;
}
return 59;
}
The answer is quite simple : you reuse your cell like you should, but never clean them
Reusing your UITableViewCell means that the cell you clicked on previously will be reused when it will go off-screen.
When clicked, you add a view to your UITableViewCell. When reused, the view is still there because you never remove it.
You have two choices : One, you could set a tag of the self.info view (or check with the indexpath you're keeping in memory), then check when you dequeue the cell if the info view is there, and remove it. The cleaner solution would be to implement the view removal by overriding the prepareForReuse method of your custom UITableViewCell
Precision
The first thing you need to do is set a tag for your self.info view after initializing it:
[self.info setTag:2222];
If you want to keep it as simple as possible, you could check and remove the self.info view directly in your cellForRowAtIndexPath method :
CategorieCell *customCell = [tableView dequeueReusableCellWithIdentifier:#"cellID" forIndexPath:indexPath];
customCell.title.text = [self.sortedDictionary objectAtIndex:indexPath.row];
if [customCell.infoView viewWithTag: 2222] != nil {
[self.info removeFromSuperview]
}
return customCell;
I am not a percent sure this code compiles, I cannot test it on my side for now. Hope it works !

UITableView Scroll automatically to the top after begin/end updates call for adjust height

I have a TableViewController that initialize my cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
FIDPostTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell for this indexPath
[cell updateFonts];
[cell loadDataWithPost:[self.posts objectAtIndex:indexPath.item]];
cell.parentTableViewController = self;
cell.indexPath = indexPath;
[cell draw];
if(self.selectedIndex == indexPath.row){
//Do expand cell stuff
} else{
//DO closed cell stuff
}
return cell;
}
and that responds to heightForRowAtIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *reuseIdentifier = CellIdentifier;
FIDPostTableViewCell *cell = [self.offscreenCells objectForKey:reuseIdentifier];
if (!cell) {
cell = [[FIDPostTableViewCell alloc] init];
[self.offscreenCells setObject:cell forKey:reuseIdentifier];
}
// Configure the cell for this indexPath
[cell updateFonts];
[cell loadDataForHeightCalculationWithPost:[self.posts objectAtIndex:indexPath.item]];
[cell draw];
if(self.selectedIndex == indexPath.row){
return [cell calculateHeight] + 100;
} else{
return [cell calculateHeight];
}
}
self.selectedIndex is a int local variable of TableViewController
Each custom cell have inside a button, that respond to a selector when touched, this is my CustomViewCell code:
self.expandSocialAction = [UIButton buttonWithType:UIButtonTypeRoundedRect];
self.expandSocialAction.backgroundColor = [FIDUIHelper fideniaLightBlue];
[self.expandSocialAction addTarget:self action:#selector(selectRow:) forControlEvents:UIControlEventTouchUpInside];
[self.contentView addSubview:self.expandSocialAction];
and then:
-(void)selectRow:(id)sender{
if(self.parentTableViewController.selectedIndex == self.indexPath.row){
self.parentTableViewController.selectedIndex = -1;
} else{
self.parentTableViewController.selectedIndex = self.indexPath.row;
}
[self.parentTableViewController.tableView beginUpdates];
[self.parentTableViewController.tableView endUpdates];
}
The cell have a pointer to parent tableViewContorller: self.parentTableViewController.
All work fine, after beginUpdate and endUpdates call, the method heightForRowAtIndexPath is called ( i put a break point in ) and also che cell have the right height.
If i click the button on the first or second row the cell animate and change height fine, but if i scroll down the table and for example i click on the 6th row, the height change but the tableView scroll automatically to the first or second element.
Any suggestion?
Regards,
I have a feeling that the heightForRowAtIndexPath: method is part of the problem. Instead of performing the calculation, try to just use two constants in that method: one for expanded, one for collapsed. I'm wondering if the method is taking too long to execute and then thinks the height of the rows are 0, then the calculation finally returns, but the UITableView scroll position is not updated.
Another suggestion might be that your estimatedHeight and heightFor methods are returning two vastly different values.
check if you have used ScrollTORowAtIndexPAth someWhere in your code

Expanding and Collapsing table view cells in ios

I have a table view of custom cells and some buttons in each cell.Clicking on any of the button inside the cell will reveal another custom view below that cell.Next click on the same button will collapse the view and need this same for all cells.I tried with insertrow method on the button click but in vain.How can i do this with using only the table view delegates.
This is what i tried:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"CustomCell_For_Dashboard";
CustomCellFor_Dashboard *customCell = (CustomCellFor_Dashboard *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (customCell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCellFor_Dashboard" owner:self options:nil];
customCell = [nib objectAtIndex:0];
}
[customCell.howyoulfeelBtn addTarget:self action:#selector(buttonclicked:) forControlEvents:UIControlEventTouchUpInside];
customCell.nameLabel.text = #"test";
customCell.imgView.image = [UIImage imageNamed:#"Default.png"];
// customCell.prepTimeLabel.text = [prepTime objectAtIndex:indexPath.row];
return customCell;
}
-(void)buttonclicked:(id)sender{
NSIndexPath *indexPath = [myTable indexPathForCell:sender];
[myTable beginUpdates];
NSIndexPath *insertPath = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
[myTable insertRowsAtIndexPaths:[NSArray arrayWithObject:insertPath] withRowAnimation:UITableViewRowAnimationTop];
}
can anyone help me?
I got the same task on one project with just one thing different: There were no buttons, just tapping on cell will expand or collapse it.
There are several things you should edit in your code. First, the button method code will look something like this:
- (void) collapseExpandButtonTap:(id) sender
{
UIButton* aButton = (UIButton*)sender; //It's actually a button
NSIndexPath* aPath = [self getIndexPathForCellWithButtonByMagic:aButton];
//expandedCells is a mutable set declared in your interface section or private class extensiont
if ([expandedCells containsObject:aPath])
{
[expandedCells removeObject:aPath];
}
else
{
[expandedCells addObject:aPath];
}
[myTableView beginEditing];
[myTableView endEditing]; //Yeah, that old trick to animate cell expand/collapse
}
Now the second thing is UITableViewDelegate method:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([expandedCells containsObject:indexPath])
{
return kExpandedCellHeight; //It's not necessary a constant, though
}
else
{
return kNormalCellHeigh; //Again not necessary a constant
}
}
Key thing here is to determine if your cell should be expanded/collapsed and return right height in delegate method.
Going off of what #eagle.dan.1349 said, this is how to do it on the clicking of the cell. In storyboard, you also need to set the table cell to clip subviews, otherwise the content that would be hidden will show.
.h
#property (strong, nonatomic) NSMutableArray *expandedCells;
.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.expandedCells containsObject:indexPath])
{
[self.expandedCells removeObject:indexPath];
}
else
{
[self.expandedCells addObject:indexPath];
}
[tableView beginUpdates];
[tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat kExpandedCellHeight = 150;
CGFloat kNormalCellHeigh = 50;
if ([self.expandedCells containsObject:indexPath])
{
return kExpandedCellHeight; //It's not necessary a constant, though
}
else
{
return kNormalCellHeigh; //Again not necessary a constant
}
}
Saw this post and just wanted to give my 2 cents as my solution to this is very similar to the chosen answer (the tapping of a whole area).
Many people architect this by using just cells alone, but I believe there is a way to build this that might align better with what people are trying to achieve:
There are headers and there are cells. Headers should be tappable, and then cells underneath the headers would show or hide. This can be achieved by adding a gesture recognizer to the header, and when tapped, you just remove all of the cells underneath that header (the section), and viceversa (add cells). Of course, you have to maintain state of which headers are "open" and which headers are "closed."
This is nice for a couple of reasons:
The job of headers and cells are separated which makes code cleaner.
This method flows nicely with how table views are built (headers and cells) and, therefore, there isn't much magic - the code is simply removing or adding cells, and should be compatible with later versions of iOS.
I made a very simple library to achieve this. As long as your table view is set up with UITableView section headers and cells, all you have to do is subclass the tableview and the header.
Link: https://github.com/fuzz-productions/FZAccordionTableView
I also had a same situation and my solution was to put a button on top of the Section Title with viewForHeaderInSection method.
noOfRows defines how many rows are there in each section and button.tag keeps which button of section is pressed.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIButton *btnSection = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, tableView.frame.size.height)];
btnSection.tag = section;
[btnSection setTitle:[sectionArray objectAtIndex:section] forState:UIControlStateNormal];
[btnSection addTarget:self action:#selector(sectionButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
return btnSection;
}
- (void)sectionButtonTapped:(UIButton *)button {
sectionIndex = button.tag;
if (button.tag == 0) {
noOfRows = 3;
} else if (button.tag == 1) {
noOfRows = 1;
} else if (button.tag == 2) {
noOfRows = 2;
}
[self.tableView reloadData];
}
Hope this will help you..

Can't change image in selected custom cell

I created a custom cell to display a text and 2 images, when the user selects the cell, the image is supposed to change. I can access the properties of the cell, but can't change them :
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
CustomCell *cell = (CustomCell *)[self.tableView cellForRowAtIndexPath:indexPath];
cell.check.image = setImage:[UIImage imageNamed:#"1355327732_checkbox-checked"];
[cell.check setImage:[UIImage imageNamed:#"1355327732_checkbox-checked"]]; }
cell.check is a UIImageView
Am i missing something?
If you are using a custom cell then you can override the function setSelected:animated: like so...
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
if (selected) {
self.check.image = [UIImage imageNamed:#"1355327732_checkbox-checked"];
} else {
self.check.image = nil;
}
}
Then you don't have to do anything in the tableView code to change this. It will just work.
A better alternative to this is to keep the image the same inside self.check. Then you can just set hidden to YES or NO accordingly. This will be more performant also.
To have this so that you get multiple selections from the table then in the TableViewController put...
self.tableView.allowsMultipleSelection = YES;
This will set it so that you can select multiple rows. One tap selects and another tap deselects.
To get the selected rows you can run...
NSArray *selectedRows = [self.tableView indexPathsForSelectedRows];
Why are you calling setImage and cell.check.image on the same line? Try this and see if the result is the same.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
CustomCell *cell = (CustomCell *)[self.tableView cellForRowAtIndexPath:indexPath];
//cell.check.image = setImage:[UIImage imageNamed:#"1355327732_checkbox-checked"];
[cell.check setImage:[UIImage imageNamed:#"1355327732_checkbox-checked"]];
}
I noticed two issues in your code
1) This code :
cell.check.image= setImage:[UIImage imageNamed:#"1355327732_checkbox-checked"];
2) There is no extension provided for the image
Replace it with:
cell.check.image= [UIImage imageNamed:#"1355327732_checkbox-checked.png"];

Resources