Having problems with the method: prepareForReuse - ios

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 !

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

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

iOS - How to neglect dequed cell after change in UItableView

I have a simple tableView in which i change the cell whenever i click it in the function like this
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[self shuftleCellExpansion:indexPath.row];
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath,nil] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
}
Second Functions:
-(BOOL) shuftleCellExpansion:(int) indexP
{
if([opendedCells containsIndex:indexP])
{
[opendedCells removeIndex:indexP];
return YES;
}
else
{
[opendedCells addIndex:indexP];
return YES;
}
}
Cell for index Function:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if([self isCellInExpandedList:indexPath.row])
{
NSLog(#"Cell is in Expanded State %hhd for Index Path %ld",[self isCellInExpandedList:indexPath.row], (long)indexPath.row);
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"poCell"];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"poCell"];
cell.backgroundColor = [UIColor blueColor];
}
[[cell textLabel] setText:[searchResults objectAtIndex:indexPath.row]];
[cell imageView].image = [UIImage imageNamed:#"someicon.png"];
return cell;
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"poCell"];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"poCell"];
cell.backgroundColor = [UIColor redColor];
}
[[cell textLabel] setText:[searchResults objectAtIndex:indexPath.row]];
return cell;
}
No the issue is that when ever i change a specific cell and then scroll it the cell at after the specific cells (i.e. after total visible cells) i see the same changed cell .
What is the efficient way to get Rid of this?
Every cell which go off the screen goes to reuse pool (mostly all data are erase) and if you scroll back the cell is populated in cellForRowAtIndexPath: method again.
You need to change the data source if you want to keep the changes.
In your didSelectRowAtIndexPath: you need to also make the changes in the array (I assume you use array to read the data from in cellForRowAtIndexPath:) you use in cellForRowAtIndexPath: to populate cell.
Hope this help.
// EXTENDED
To make it work you need to amend shuftleCellExpansion: method. In opendedCells you swap the indexes, I believe it reflect the changes in table view. In the same method you have to reflect the changes for searchResults array as well (swap the object as you did for opendedCells).

didSelectRowAtIndexPath not working

I am having issues with my tableView not firing the didSelectRowAtIndexPath method. I have implemented the delegates as such:
#interface ViewController : UIViewController<UITableViewDataSource, UITableViewDelegate,UIScrollViewDelegate>
And in my storyboard the tableView's data source and delegate are both pointed at the base View Controller. I have User Interactions enabled as well as Selection set to Single Selection, and it is not the TapGesture problem since my tap gestures are not bound to the view and I have checked and they do not fire.
This is the code for setting up the table:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return menuArray.count;
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"menuCell"];
NSDictionary *menuItem = [menuArray objectAtIndex:indexPath.row];
cell.textLabel.text = menuItem[#"Title"];
cell.detailTextLabel.text = menuItem[#"Subtitle"];
return cell;
}
-(void)showMenu{
[UIView animateWithDuration:.25 animations:^{
[content setFrame:CGRectMake(menuTable.frame.size.width, content.frame.origin.y, content.frame.size.width, content.frame.size.height)];
}];
}
-(void)hideMenu{
[UIView animateWithDuration:.25 animations:^{
[content setFrame:CGRectMake(0, content.frame.origin.y, content.frame.size.width, content.frame.size.height)];
}];
}
-(IBAction)showMenuDown:(id)sender {
if(content.frame.origin.x == 0)
[self showMenu];
else
[self hideMenu];
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//whatever
}
The table is initially out of view on the storyboard (origin.x is set to -150), then when the user clicks on a button in the navigationBar, the view slides over to reveal it, which is what might be causing the problem I think.
Is there anything wrong with my code or implementation that would be causing this to not work?
If you already see your table populated with values from your dictionary then you can rule out data source and delegate as being the problem. i.e. your storyboard connections are working.
Your code looks fine to me. the only difference I see is I usually define my table like this. Try this and see if it helps.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//NSLog(#"Inside cellForRowAtIndexPath");
static NSString *CellIdentifier = #"Cell";
// Try to retrieve from the table view a now-unused cell with the given identifier.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// If no cell is available, create a new one using the given identifier.
if (cell == nil)
{
// Use the default cell style.
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
//Your code here
// ....
return cell;
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"menuCell"];
This will return nil in case there was never a cell created.
so checking if cell is nil is mandatory and if so, you need to create a cell.
static NSString *CellIdentifier = #"menuCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
}
as you are using storyboard you can alternatively use
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
for prototype cells. Make sure you use the same identifier in the storyboard and that you registered your the cell's class
- (void) viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"menuCell"];
}

How to set custom background color of UITableViewCell even there's no data inside

I want to accomplish something like this :
see there's only one data but, background color continue until end.
I understand I can do inside tableview delegate of tableView:willDisplayCell:forRowAtIndexPath:. but then it doesn't go to empty cell, hence my empty cell always be white.
I used the following code to display cell alternative color even if cell is not initialized.I have done this work on scrollViewDidScroll as showing below:--
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
UIView *view=[[UIView alloc] initWithFrame:tblView.frame];
view.backgroundColor=[UIColor greenColor];
UIView *cellView;
int y=0;
int i=0;
for (UIView *view in tblView.subviews) {
if ([NSStringFromClass([view class]) isEqualToString:#"_UITableViewSeparatorView"]) {
cellView=[[UIView alloc] initWithFrame:CGRectMake(0, y, 320, 44)];
if (i%2==0) {
cellView.backgroundColor=[UIColor redColor];
}else{
cellView.backgroundColor=[UIColor greenColor];
}
[view addSubview:cellView];
i++;
}
}
tblView.backgroundView=view;
}
And got the correct result on scrolling table view. But the problem is it works when user scrolls the tableView atleast once a time.
If you will get success to fire event on tableView completes its reloading.Then it will be fine.
Here is output I got on scrolling tableView.
I also write this method to call didScrollMethod manually but doesn't seems to work perfectly.
[tblView.delegate scrollViewDidScroll:(UIScrollView*)tblView.superclass];
But calling method like code below absolutely works fine.
- (void)viewDidLoad
{
tblView=[[MyFirstView alloc] init];
tblView.delegate=self;
[tblView setFrame:self.view.frame];
[self.view addSubview:tblView];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
-(void)viewDidAppear:(BOOL)animated{
[tblView.delegate scrollViewDidScroll:(UIScrollView*)tblView.superclass];
}
Means after loading tableView in viewDidLoad call didScroll in viewDidAppear works fine.
Insert below code if fluctuates first row while scrolling.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *view=[[UIView alloc] init];
return view;
}
You have to set the backgroundColor to the contentView of a UITableViewCell.
Sample as below:
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"identifier"];
if (cell==nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"identifier"]autorelease];
cell.contentView.backgroundColor= [UIColor greenColor];
}
return cell;
}
To have alternate colors in your cells of tableView, you can do the following;
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"identifier"];
if (cell==nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"identifier"]autorelease];
}
if(indexPath.row % 2)
{
cell.contentView.backgroundColor= [UIColor greenColor];
}
else
{
cell.contentView.backgroundColor= [UIColor yellowColor];
}
return cell;
}
A table with plain style doesn't show rows below the last row so there is no way to produce the desired effect using table view cells. About your only option would be to create a view with the alternating pattern and make the view the table view's footer view.
This view would need to deal with being updated as the number of actual rows in the table changes to/from odd and even. And you need to make it tall enough so if the user scrolls the table up a bunch, the footer still reaches the bottom of the screen.
You can setup some placeholder cells in addition to your cell with 'Monthly meeting', something like:
return amount of rows as 1 + (rows to fill screen) in the tableView:numberOfRowsInSection:
In the tableView:cellForRowAtIndexPath: - check for index path of the cell, if its row = 0, then this is your action cell, otherwise, update cells background, do the same in the tableView:willDisplayCell:forRowAtIndexPath:. Make sure to remove selectionStyle for your placeholder cells.
Or, you can use 2 cells - first one - again, your 'Monthly meeting' cell, and second one - a cell with height enough to cover screen from first cell to the bottom with image of striped cells.
This is simple to do. Just have as many items in your data source array as you want to see rows, and have all but the first one be empty strings. In willDisplayCell:forRowAtIndexPath: apply a background color to all the odd numbered cells.
- (void)viewDidLoad {
[super viewDidLoad];
self.theData = #[#"Monthly Meeting",#"",#"",#"",#"",#"",#"",#"",#"",#""];
[self.tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.theData.count;
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row % 2 == 1) {
cell.backgroundColor = [UIColor colorWithRed:232/255.0 green:238/255.0 blue:222/255.0 alpha:1];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.textLabel.text = self.theData[indexPath.row];
return cell;
}
Set backgroundColor to the contentView of a UITableViewCell with the help of simple mathematics, Example:
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"identifier"];
if (cell==nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"identifier"]autorelease];
if (i%2==0) {
cell.contentView.backgroundColor= [UIColor greenColor];
}else{
cell.contentView.backgroundColor= [UIColor redColor];
}
}
return cell;
}

Resources