iOS table view cell doesn't show expected behaviour - ios

Yesterday I asked a question about a cell not showing correctly a button that depends on a string value. Please take a look at it if you want: Strange behaviour on table view with core data.
User #jrturton pointed out following as part of his answer:
A reused cell will have the subview added every time - so there could
be many urgent views on top of each other. The cell should only ever
add this once and keep it in a property
I think that this answer marks the correct direction I must follow to solve my issue, but I am not able to implement the answer into my code which is following:
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
NSManagedObject *managedObject = [fetchedResultsController objectAtIndexPath:indexPath];
NSString *isUrgent = [[managedObject valueForKey:#"urgent"]description];
[[cell textLabel] setText:[[managedObject valueForKey:#"thingName"] description]];
//urgent
if ([isUrgent isEqual:#"Urgent"]){
UIButton *urgentButton = [[UIButton alloc]initWithFrame:CGRectMake(71, 27, 18, 18)];
[urgentButton setImage:[UIImage imageNamed:#"urgent-3"]forState:UIControlStateNormal];
[cell addSubview:urgentButton];
NSLog(isUrgent);
}
//not urgent
if ([isUrgent isEqual:#"Not urgent"]){
UIButton *urgentButton = [[UIButton alloc]initWithFrame:CGRectMake(71, 27, 18, 18)];
[urgentButton setImage:[UIImage imageNamed:nil]forState:UIControlStateNormal];
[cell addSubview:urgentButton];
NSLog(isUrgent);
}
[[cell detailTextLabel] setText:#" "];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
cell.textLabel.textColor = [UIColor blueColor];
cell.textLabel.font = [UIFont fontWithName:#"Noteworthy" size:22.0f];
cell.detailTextLabel.font = [UIFont fontWithName:#"Noteworthy" size:15.0f];
}
The behaviour of the cell must be following:
1. If isUrgent = #"Urgent", the cell must show urgentButton (including imageNamed:#"urgent-3":
2. Else no button has to be shown.
The current behaviour is as follows:
1. If isUrgent = #"Urgent", the cell shows urgentButton (including imageNamed:#"urgent-3".
2. If isUrgent = #"Not urgent", value tested in NSLog, the cell shows urgentButton too.
This behavior only happens when the cell has changed its isUrgent value at least one time.
I need your help to implement the above mentioned answer. Thank you.

I agree with #wuii, but I think the answer can be clearer. The idea is that reused cells have their view hierarchy already built, so it's harmful to do it again each time a cell is reused (which is all of the time during scrolling). The advice can be encapsulated in a "lazy getter" that returns the cell's urgent button.
// above #implementation
#define kURGENT_BUTTON_TAG (256)
- (UIButton *)urgentButtonInCell:(UITableViewCell *)cell {
UIButton *urgentButton = (UIButton *)[cell viewWithTag:kURGENT_BUTTON_TAG];
if (!urgentButton) {
urgentButton = [[UIButton alloc]initWithFrame:CGRectMake(71, 27, 18, 18)];
urgentButton.tag = kURGENT_BUTTON_TAG;
[cell addSubview:urgentButton];
}
return urgentButton;
}
Now your configureCell can just ask for the button:
UIButton *urgentButton = [self urgentButtonInCell:cell];
UIImage *image = ([isUrgent isEqualToString:#"Urgent"])? [UIImage imageNamed:#"urgent-3"] : nil;
[urgentButton setImage:image forState:UIControlStateNormal];

You will need to keep track of your cell is reused or it's fresh one.
What here might be happening is you are adding UIButton to reused cell, but it already has button having "urgent-3" image set
Do this
Pass one more BOOL parameter to configure cell isFreshCell with value set as per new cell or not.
**
MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
isFreshCell = NO;
if(cell==nil)
{
isFreshCell = YES;
cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
[self configureCell:cell atIndexPath:indexPath isFreshCell:isFreshCell];
New method signature
-(void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath isFreshCell:(BOOL) isFreshCell
2 When you add button set some tag to it.
3 If isFreshCell is false dont add new button as it is already there and you can access this button using subviewWithTag method like this cell.contentView.subviewWithTag and then set image or set nil image or just hide button

Related

iOS7: UILabel constantly redraw itself on UITableViewCell

CoreData returns BOOL value and according to the value I draw a UILabel on UITableViewCell accessoryView. The problem is that UILabel repeats itself also on the cells it shouldn't appear at all.
CGRect lblRect = CGRectMake(230, 7, 20, 20);
UILabel *lblEnabled = [[UILabel alloc] initWithFrame:lblRect];
lblEnabled.textColor=[UIColor whiteColor];
lblEnabled.textAlignment=NSTextAlignmentCenter;
[lblEnabled setFont:[UIFont fontWithName:#"Verdana" size:10.0]];
[lblEnabled setText:#"40"];
lblEnabled.backgroundColor= [UIColor colorWithPatternImage:[UIImage imageNamed:#"greenBg"]];
lblEnabled.layer.cornerRadius = 9.0;
lblEnabled.layer.masksToBounds = YES;
lblEnabled.tag = indexPath.row;
cell.accessoryView = lblEnabled;
[cell.contentView addSubview:lblEnabled];
So it appears sometimes on the cell where the BOOL value = NO; Your help is strongly appreciated.
EDIT: I draw these labels in cellForRowForIndexPath.
EDIT: I use storyboards, so I don't check if cell is nil.
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier=#"Cell";
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
/*
if(cell==nil)
{
cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
tableView.rowHeight=57.0;
}*/
Coin *coin=[[self frcFromTV:tableView ] objectAtIndexPath:indexPath];
cell.textLabel.text=coin.coinNominal;
if(coin.comSubject.length>0)
{
cell.detailTextLabel.text=[NSString stringWithFormat:#"%#%# (%#) | %#",[self returnCatalogDefinition:coin.catalogIndex],coin.kmRef, coin.dates, coin.comSubject];
}
else
{
cell.detailTextLabel.text=[NSString stringWithFormat:#"%#%# | %#",[self returnCatalogDefinition:coin.catalogIndex],coin.kmRef,coin.dates];
}
if(coin.isCommemorative.boolValue)
{
// implement label
}
if(coin.listed.boolValue)
{
CGRect lblRect = CGRectMake(230, 7, 20, 20);
UILabel *lblEnabled = [[UILabel alloc] initWithFrame:lblRect];
lblEnabled.textColor=[UIColor whiteColor];
lblEnabled.textAlignment=NSTextAlignmentCenter;
[lblEnabled setFont:[UIFont fontWithName:#"Verdana" size:10.0]];
[lblEnabled setText:#"40"];
lblEnabled.backgroundColor= [UIColor colorWithPatternImage:[UIImage imageNamed:#"greenBg"]];
lblEnabled.layer.cornerRadius = 9.0;
lblEnabled.layer.masksToBounds = YES;
lblEnabled.tag = indexPath.row;
cell.accessoryView = lblEnabled;
[cell.contentView addSubview:lblEnabled];
}
return cell;
}
In code below you probably add your label but because those cells are reusable you should handle else statement (hiding label or whatever is appropriate)
if(coin.isCommemorative.boolValue)
{
// implement label
//remove from that part of the statement this line:
//[cell.contentView addSubview:lblEnabled];
} else {
cell.accessoryView = nil;
//hiding or modifying label for other cases
}
if you will not deal with that else statement the change you made in if will be applicable to more than one cell because of the reusing mechanism
As a "side advice" I would recommend you to subclass UITableViewCell and add the property you want (label) to encapsulate that and make only public method for showing or hiding that accessor.
EDIT:
if your flag for a change is not specifying to which cell it has to indicate (for example using indexPath) then the result is as your one.
This is quite global state if(coin.isCommemorative.boolValue) not indicating to which cell it counts try for example (for just learning purpose) add if(coin.isCommemorative.boolValue && indexPath.row%2==0) and see the result.
Are you reusing cells? If so, then you need to remove that cell from contentView inside prepareForReuse method.

Button added to tableview cell appears on other random cells as well

I'm experiencing a problem with regards to adding a button to a tableview cell. Basically, I'm adding a button to a specific cell, but that button also appears on random other cells (not currently in the view).
When I tap a cell, I set a variable (tappedCell=indexPath), and reload that cell. In my cellForRowAtIndexPath I check if the indexPath is equal to the tappedCell, and if it is I add a button to it. That works, but this button also appears on other cells further down in the tableView. These cells are not in the view when I tap it, but further down, so I have to scroll to see them. If I keep scrolling up and down (quickly), more and more cells with buttons appears. This seems to be completely random.
I've tried adding a NSLog() inside the if-statement where I add the button, but this is not called more than once (when I tap the original cell). So it's actually not adding more buttons, it has to be the same one appearing in multiple cells.
I have no idea why this is happening. I've tried adding a button to every cell, and then just set hidden = YES as standard, and hidden = NO when the [self.tappedCell isEqual:indexPath], but this does not change anything...
Here you can see the code I use to add the button to my cell:
UIButton *newBtn=[UIButton buttonWithType:UIButtonTypeCustom];
[newBtn setImage:[UIImage imageNamed:#"open.png"] forState:UIControlStateNormal];
[newBtn setFrame:CGRectMake(220, 0, 100, 86)];
[newBtn addTarget:self action:#selector(openImage:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:newBtn];
I don't know what I'm doing wrong, or if I am doing something wrong at all. It's worth mentioning that I'm using a standard Apple/iOS style=subtitle cell, and not a custom one. This is to keep it consistent throughout my app (The cells are made in the storyboard, and I'm reusing a prototype cell).
Could anyone give me any insight on why it's behaving like it does, and how I can fix it?
Thanks in advance!
EDIT 1:
Here's my full code for cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Set up the item
XYZItem *item = [self.itemsInView objectAtIndex:indexPath.row];
NSString *CellIdentifier = #"itemPrototypeCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.textLabel.text = item.itemName;
if ([self.selectedItem isEqual:indexPath])
{
cell.textLabel.numberOfLines = 2;
UIButton *newBtn=[UIButton buttonWithType:UIButtonTypeCustom];
[newBtn setImage:[UIImage imageNamed:#"open.png"] forState:UIControlStateNormal];
[newBtn setFrame:CGRectMake(220, 0, 100, 86)];
[newBtn addTarget:self action:#selector(openImage:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:newBtn];
}
NSString *detailTextLabelText = [NSString stringWithFormat:#"%lu subitems", (unsigned long)[item.subItems count]];
cell.detailTextLabel.text = detailTextLabelText;
return cell;
}
The button is showing up on cells that have reused a button that previously added it.
There are a few options here.
Make the button part of every cell. Set it's .hidden property every time you create/reuse one to the appropriate value.
Remove the button in the cell's prepareForReuse method.
Assuming self.tappedCell is a UITableViewCell subclass and indexPath is an index path, [self.tappedCell isEqual:indexPath] will always return NO
I faced same issue and i also had multiple headers:
The solution is:
if (!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
headerLabel = [[UILabel alloc] initWithFrame:
CGRectMake(15, 5, self.tableView.frame.size.width, 15.0)];
headerLabel.backgroundColor = [UIColor clearColor];
headerLabel.textAlignment = NSTextAlignmentLeft;
[headerLabel setFont:[UIFont fontWithName:#"Helvetica Neue Medium" size:13.0]];
headerLabel.tag = 1002;
[cell.contentView addSubview:headerLabel];
button = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, HEIGHTROW)];
[button addTarget:self action:#selector(eventAddAlert) forControlEvents:UIControlEventTouchUpInside];
[button setBackgroundImage:[UIImage imageNamed:#"add_manually.png"] forState:UIControlStateNormal];
button.hidden = YES;
button.tag = 1001;
[cell.contentView addSubview:button];
}else{
// use viewWithTag to find lblNombre in the re-usable cell.contentView
headerLabel = (UILabel *)[cell.contentView viewWithTag:1002];
button = (UIButton *)[cell.contentView viewWithTag:1001];
}
if(indexPath.section == 0 && indexPath.row == 0){
button.hidden = NO;
}else{
button.hidden = YES;
.........
}
.........

Make TableView buttons take up less memory

I have code here that generates a list of cells that contain buttons.
I'm pretty sure there is a way to make it so that I am creating 1 button object instead of 20, but I'm not quite sure how to implement that?
I need some guidance as to where to make it so I only create 1 button object.
Additionally, I might be making 20 cell objects when I only need 1 as well?
I don't totally understand how to utilize memory well yet.
My code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier = #"TimesCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
//[cell sizeToFit];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
if([cell.contentView viewWithTag:3] != nil){
[[cell.contentView viewWithTag:3] removeFromSuperview];
}
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button addTarget:self
action:#selector(customActionPressed:)
forControlEvents:UIControlEventTouchDown];
[button setTitleEdgeInsets:UIEdgeInsetsMake(-10.0f, 0.0f, 0.0f, 0.0f)];
button.frame = CGRectMake(5, 5, 310, 50);
button.tag = 3;
int rows = [yourDefaultActivities count];
int rowsl = rows - 1;
float r = 1+(((255-1+75)/rowsl)*(indexPath.row));
if(r > 255){
r = 255;
}
float g = 255-(((255-1+75)/rowsl)*(indexPath.row));
if(g < 0){
g = 255-fabsf(g);
}else{
g = 255;
}
float b = 1;
UIColor *thisColor = [UIColor colorWithRed:r/255 green:g/255 blue:b/255 alpha:1];
button.backgroundColor = thisColor;
button.clipsToBounds = YES;
button.layer.cornerRadius = 15;
button.titleLabel.font = [UIFont fontWithName:#"HelveticaNeue" size:14];
[button setTitle:[[yourDefaultActivities objectAtIndex:indexPath.row] objectAtIndex:0] forState:UIControlStateNormal];
[button.titleLabel setTextColor:[UIColor blackColor]];
[button.titleLabel setFont:[UIFont fontWithName:#"HelveticaNeue" size:14]];
[cell addSubview:button];
return cell;
}
EDIT: I tried to remove the buttons every time I created a new one, but I must be doing it wrong..
One button object can't appear in 8 cells at once -- you do need as many cells and buttons as will fit on the screen at any time (it looks like 8 or 9). But 8 or 9 cell with buttons in them should not cause any memory problems -- buttons aren't that expensive. The problem with the code you posted though, is that it is adding buttons every time cellForRowAtIndexPath is called, which happens a lot when you scroll. Since cells are reused, you don't want to be adding a button to a cell that already has one. The easiest solution, in my opinion, is to make a custom cell in the storyboard, and add you buttons there. You can still set their color in code based on the indexPath so you get the look you want. Alternately, you can check whether the cell (actually the cell.contentView which is where you should be adding them, not directly to the cell) has a subview of class UIButton, and only add a button if it doesn't already have one.
After Edit:
In answer to your comment, you don't need to make the rounded button in the storyboard, you can just add a button (type custom) to your subclassed cell, and then modify its look in code. Here is an example of what I mean. In this test app, I created a custom cell class (RDCell) and changed the class of the cell in the storyboard to that. I added a custom button to the cell, positioned and sized it with constraints, and made an IBOutlet to it in RDCell.h. Here is what I have in the table view controller:
#import "TableController.h"
#import "RDCell.h"
#implementation TableController
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 20;
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(RDCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
cell.button.backgroundColor = [UIColor colorWithHue:.15 + (indexPath.row/30.0) saturation:1 brightness:1 alpha:1];
cell.button.layer.cornerRadius = 15;
cell.button.titleLabel.textColor = [UIColor blackColor];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RDCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
[cell.button addTarget:self action:#selector(customActionPressed:) forControlEvents:UIControlEventTouchUpInside];
cell.button.tag = indexPath.row;
return cell;
}
-(void)customActionPressed:(UIButton *) sender {
NSLog(#"button pressed in row: %d",sender.tag);
[self.tableView reloadRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:sender.tag inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
}
This was the resultant view:
I put a log in initWithCoder for RDCell, and it's called only 12 times (the max. number of cells on the screen at any one time), so there's never more than 12 cells or 12 buttons.

invalid nib registered for identifier (Cell)

Getting the following error in my UITableView when I drag a segue to another view controller to be pushed.
"invalid nib registered for identifier (Cell) - nib must contain exactly one top level object which must be a `UITableViewCell` instance"
I have a UITableView on my storyboard. I put in two static rows in the UITableView via storyboards. I have given these rows a cellIdentifier of "Cell" via the storyboard.
HERE IS MY cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
[cell.textLabel setFont:[UIFont fontWithName:#"GothamRounded-Light" size:18]];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(40, 13, 300, 30)];
label.textColor = [UIColor statLightBlue];
[label setFont:[UIFont fontWithName:#"Rounded-Light" size:18]];
[cell.contentView addSubview:label];
if(indexPath.row == 0){
label.text = #"Credit Card"; // man profile
cell.imageView.image = [UIImage imageNamed:#"CreditCardIcon.png"];
} else if (indexPath.row == 1) {
label.text = #"Promotion Code"; // credit card
cell.imageView.image = [UIImage imageNamed:#"PromoCodeIcon.png"];
}
return cell;
}
At this point, it all works great. But here is what I do that causes the error above:
If I control drag from the first UITableView cell, in storyboards, to any other random view creating a segue, it causes the error above. If I remove said segue the error goes away.
Any clue on what I am doing wrong?
the problem is that you give "CELL" and no "Cell" have also a double check if you set the identifier in IB or the Restoration ID, the identifier that you have to set is in "Attributes inspector"
Ok, the issue is I had dynamic cells picked in the attribute inspector, but had the code in cellForRowAtIndex wrong.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [super tableView:tableView
cellForRowAtIndexPath:indexPath];
[cell.textLabel setFont:[UIFont fontWithName:#"Rounded-Light" size:18]];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(40, 13, 300, 30)];
label.textColor = [UIColor statLightBlue];
[label setFont:[UIFont fontWithName:#"Rounded-Light" size:18]];
[cell.contentView addSubview:label];
if(indexPath.row == 0){
label.text = #"Credit Card"; // man profile
cell.imageView.image = [UIImage imageNamed:#"CreditCardIcon.png"];
} else if (indexPath.row == 1) {
label.text = #"Promotion Code"; // credit card
cell.imageView.image = [UIImage imageNamed:#"PromoCodeIcon.png"];
}
return cell;
}
I got the similar error using storyboard. Fix it by changing Table View's "Prototype Cells"#"Attributes inspector" to 1.

Why does my accessory button move to the left for taller table cells?

I have a table in my iOS app with three table cells, and they each have a custom Accessory button. One of the cells needs to be taller than the others; it is 60px instead of 45px. In this case, the accessory button gets scooted over to the left, whereas if they were all the same height, the accessory button would line up.
The accessory buttons are created by the same code, so they should be identical. The issue seems to be related to the UITableViewCell itself.
It ends up looking like this. I failed to include the upper border in the screen grab, but the upper cell is the taller one. Does anyone know how I could fix this?
Here's an example of how the cells are created. These differ only in name; the height is specified by tableView: heightForRowAtIndexPath:
cell = [[UITableViewCell alloc] init];
label = [[UILabel alloc] initWithFrame:[cell frame]];
[label setText:#"Favorites"];
[cell.contentView addSubview:label];
[cell setAccessoryType:UITableViewCellAccessoryDetailDisclosureButton];
button = [UIButton buttonWithType:UIButtonTypeCustom];
image = [UIImage imageNamed:#"GT.png"];
[button setBackgroundImage:image forState:UIControlStateNormal];
button.frame = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
[cell setAccessoryView:button];
You are setting the accessory view and the accessory type. Do one or the other. I would get rid of setAccessoryView:button.
[cell setAccessoryType:UITableViewCellAccessoryDetailDisclosureButton];
[cell setAccessoryView:button];
Also, why are you doing this:
cell = [[UITableViewCell alloc] init];
You should be creating your cell in cellForRowAtIndexPath you should having something like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = nil;
if (cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = #"Some Text";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}

Resources