UITableView Cell Label Duplicating - iphone

I have a UINavigationController full of UITableViews and I use custom cells in a specific one of these.
In tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath I add a UILabel with [cell addSubview:label];
This works great, however if I then go into the detail view, then return back, it duplicates the label. How can I stop this?
Thanks.

The problem is that you are adding a new label every time the cell gets reused and displayed again.
Since you're using a custom cell already, the easiest solution would be to give the cell a UILabel property, and use that instead of adding a new label each time. Specifically, you should only add a new label if you're creating a cell instead of getting a recycled one. Or, you could add a new UILabel only if the property is nil.

Or as an alternative to adding the label in the custom class, just make sure to add the label inside the conditional cell creation. e.g.
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:ident];
if(cell == nil){
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:ident] autorelease];
[cell.contentView addSubview:[[[UILabel alloc] init] autorelease]]; // <-------
}

Add a tag to the label, then check if it exists
UILabel *label;
if ([cell viewWithTag:2]){
label = [cell viewWithTag:2];}
else{
label = [[UILabel alloc]init];label.tag = 2;}

Related

A workaround to get textAlignment to center in UITableViewCellStyleSubtitle

According to this question (and many many google search results), when we're in a table view, and we set the cells to have UITableViewCellStyleSubtitle, we have been forced to align our texts inside the cell label to be on the left.
However I want one of the rows (in fact the row) of my table view to be aligned center... Obviously this wouldn't work:
cell.textLabel.textAlignment = UITextAlignmentCenter;
So I've been thinking what I should do instead... I'm not sure how to do it cleanly. The best way I can think of is to change this particular cell so that the style of the cell is UITableViewCellStyleDefault, but I don't think the cell property is changeable. Another way that I can think of is to subclass the UITableViewCell class, but that seems to be a bit of an overkill just for one table view cell. What do you think I should do to make that one last row aligned center?
EDIT: for those of you who're interested in the answer:
static NSString *CellIdentifier = #"Cell";
static NSString *CellIdentifierDefault = #"CellDefault";
UITableViewCell *cell; // = [pTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (indexPath.row >= [data count])
cell = [pTableView dequeueReusableCellWithIdentifier:CellIdentifierDefault];
else
cell = [pTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil && indexPath.row < [data count]) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
else if(cell == nil && indexPath.row >= [data count]) {
//Use default style for the last cell.
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifierDefault] autorelease];
}
#the_great_monkey:
You can change the cellIdentifier for the cell where you want this center alignment.
For the other cells you should keep one common cellIdentifier.
Then when you do this
cell.textLabel.textAlignment = UITextAlignmentCenter;
cell.detailTextLabel.textAlignment = UITextAlignmentCenter;
Then simply you need to check if cellIdentifier is the one for the cell which requires center alignment?
If Yes, then apply,
cell.textLabel.textAlignment = UITextAlignmentCenter; and cell.detailTextLabel.textAlignment = UITextAlignmentCenter; for that cell.
Hope this helps you.
It's easy to load a cell from a .xib, which lets you arrange the cell's subviews any way you like. Unfortunately, UITableViewCell's textLabel and detailTextLabel properties aren't IBOutlets, so you can't connect the labels that you'd like to use for these properties in IB.
One way to handle this is to connect those views to outlets in your controller instead, and use them when you set up the cell in -tableView:cellForRowAtIndexPath:. That's fine if you won't need to change the content of the cell later. In that case, you can just create a nib containing a UITableViewCell, set the type of File's Owner to match your controller, and set up the cell's subviews however you like and connect them to File's Owner. See Loading Custom TableView Cells from Nib Files for more information.
The other way to go is to create a UITableViewCell subclass that has outlets for the text label and detail label. Then do the same as above, but use your subclass rather than UITableViewCell. Once you've created this subclass, you can use it with different .xibs if you want different layouts at some point. The work needed to create the subclass is minimal, and it becomes a useful and reusable tool.
cell.textLabel.textAlignment=UITextBorderStyleLine;

Can't get texts on UITableViewCell

I want to get multiple custom texts on cell of UITableView. I want these texts after clicking on a dynamically added button on the cell. I can't use didRowSelectedAtIndex method. I have my own selector which I am executing but I don't know how can I get texts on each cell. Anybody can help me?
Thank
You add a UILabel to the cell's view. Just like you did it with your custom Button.
You can add a UILabel to all cells in advance. If the UIButton on a cell is cklicked, you have to save that state information somewhere (NSArray). When it comes to rendering the cell, you ask the array if the cell at current index should show some text and set it up if so...
If you want immediate rendering call [self.tableView reloadData]
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if (array at index indicates clicked) {
myCustomCell.label1 = #"foo";
} else {
myCustomCell.label1 = #"bar";
}
}
If you don't like customs cells. You can apply a tag to the label, add the label to the cell and get it from cell by querying it afterwards by that tag.
Make your labels as global variables
Create yur cell as default style cell.
Add your labels to contentView of cell without text.
Add your button.
In the method that button invokes after click set texts to your labels.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
customLabel1 = [[UILabel alloc] initWithFrame:CGRectMake(5, 5, 50, 20)];
customLabel2 = [[UILabel alloc] initWithFrame:CGRectMake(55, 5, 50, 20)];
[cell.contentView addSubview:customLabel1];
[cell.contentView addSubview:customLabel2];
}
and
- (IBAction) click
{
customLabel1.text = #"YAYA";
customLabel2.text = #"GAGA";
}
You could maybe try creating a custom button class by extending UIButton and add properties to contain references to the NSString objects (or index of the original array where the strings are stored etc).
Then on button click you can cast the sender to your custom button type and simply retrieve the property values.

Force UITableView to dump all reusable cells

I have a UITableView where I have the backgroud color set via
UIView *myView = [[UIView alloc] init];
if ((indexPath.row % 2) == 0)
myView.backgroundColor = [UIColor greenColor];
else
myView.backgroundColor = [UIColor whiteColor];
cell.backgroundView = myView;
[myView release];
The problem I find is that when I edit a table (via setEditing:YES...) some cells of the same color invariable are next to each other. How do I force UITableView to fully redraw. reloadData is not doing a great job.
Is there are deep-cleaning redraw?
I had this issue before so I'll share with you how I solved it:
You can use a boolean flag (say it's called needsRefresh) to control the behavior of cell creation in -cellForRowAtIndexPath:
An example:
- (UITableViewCell*) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath {
UITableViewCell *cell = [tableView dequeueResuableCellWithIdentifier:SOME_ID];
if(!cell || needsRefresh) {
cell = [[[UITableViewCell alloc] init....] autorelease];
}
//.....
return cell;
}
So, when you need a hard reload, set the needsRefresh flag to YES. Simple as a pimple.
For me the accepted answer didn't really work since I had no idea when to set the needsRefresh back to YES.
What worked for me was:
- (UITableViewCell*) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath {
UITableViewCell *cell = [tableView dequeueResuableCellWithIdentifier:customCellIdentifier];
if(nil == cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:customCellIdentifier];
}
//.....
return cell;
}
And then you change the customCellIdentifier value whenever you need to. This way the cells are also still reusable if you switch back to the original cell identifier.
The accepted method seems dirty, it just makes a bunch of new cells that are stored along with the bad ones. Here are a couple of solutions depending on your situation:
1.
first, for the situation described in the question you should not dump your cells and create new views on every cycle. You need to tag your view and then get it back when from the cell when you get a reuse cell:
- (UITableViewCell*) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath {
UITableViewCell *cell = [tableView dequeueResuableCellWithIdentifier:SOME_ID];
if(!cell) {
cell = [[UITableViewCell alloc] init];
UIView *myView = [[UIView alloc] init];
cell.backgroundView = myView;
[myView setTag:5]; //<------
}
UIView *myView = [cell viewWithTag:5]; //<------
if ((indexPath.row % 2) == 0)
myView.backgroundColor = [UIColor greenColor];
else
myView.backgroundColor = [UIColor whiteColor];
return cell;
}
//then just reload the tableview.
2.
...or even better, why not just use the cell backgrouncolor and update that without creating a view.
3.
A sure way to really clear out old cached cells it to simply recreate the UITableView object.
4.
In most cases you dont need to destroy these cells, just keep track of your elements and update them after getting the reusable cell.You can tag all your elements, keep a array reference to them, find them thought the view hierarchy... Im sure theres a bunch of other ways.
5.
heres a one liner to directly purge all cells, although not best practice to mess with the internals of objects like this as they might change in future versions:
[(NSMutableDictionary*)[tableview valueForKey:#"_reusableTableCells" ] removeAllObjects];
I was able to solve this by adding a refresh variable to the table datasource. I used a dictionary for each cell, but there's an extra key called #"refresh":#"1", indicating the cell needs refreshing. Once it's updated, I set that key's value to #"0". So whenever the table is reloaded, make sure the key goes back to #"0" again.
#define TABLE_VIEW_CELL_DEFAULT_ID #"cellIdentifier"
#property (nonatomic, strong) NSString *tableViewCellIdentifier;
#property (nonatomic) NSUInteger tableViewCellIdentifierCount;
// By using a different cell identifier, this effectively flushes the cell
// cache because the old cells will no longer be used.
- (void) flushTableViewCellCache
{
self.tableViewCellIdentifierCount++;
self.tableViewCellIdentifier = [NSString stringWithFormat:#"%#%i", TABLE_VIEW_CELL_DEFAULT_ID, self.tableViewCellIdentifierCount];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.tableViewCellIdentifier];
if (cell == nil) {
cell = [[MyTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:self.tableViewCellIdentifier];
}
// rest of method...
}

How can i make my UITableViewCell contain two labels?

I want my UITableViewCell to look like the image below where there seems to be two labels. Is this possible without subclassing UITableViewCell?
alt text http://img31.imageshack.us/img31/2764/photoobp.jpg
There are different styles of UITableVieWCell. See here:
https://developer.apple.com/documentation/uikit/uitableviewcell/cellstyle
I think you want to use UITableViewCellStyleValue1.
You can initialise your UITableViewCell with the relevant style:
https://developer.apple.com/documentation/uikit/uitableviewcell/1623276-init
When you use a style that has two labels, you can use the textLabel and detailTextLabel properties to set them, respectively.
You do not need to subclass a UITableViewCell in order to add content to it. Here could be a sample cell generation method with an extra label:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"Identifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
UILabel *secondLabel = [[UILabel alloc] initWithFrame:cell.textLabel.frame];
secondLabel.textAlignment = UITextAlignmentRight;
secondLabel.tag = 12345;
[cell.contentView addSubview:secondLabel];
}
UILabel *second = [cell viewWithTag:12345];
second.text = #"Second!";
return cell;
}
Let me know if you have any questions. I can clarify some things if needed.
Not sure where u think you see 2 labels...you can set the UILabels number of lines property if you want more lines UILabel ref....Also there is a UITableViewCell type UITableViewCellStyleSubtitle
which contains a detailTextLabel on top of the regular text labels in UITableCell, so you already have a built in cell with 2 text fields, here is a ref ref to UITableViewCell
Its not 2 labels but 2 buttons, you need to add 2 buttons in contentView view of the cell. Or you can create a footer or header View and add these 2 buttons.

What is a good way to animate UIViews inside UITableViewCell after a callback?

We couldn't find a way to animate UIViews inside a UITableCell as an action from a callback.
Suppose in a scenario where you click on a button on the UITableViewCell and it fires off an asynchronous action to download a picture. Suppose further that when the picture is downloaded we want a UIView in the cell to animate the picture to give user a visual feedback that something new is about to be presented.
We couldn't find a way to track down the UIVIew to invoke beginAnimation on because the original cell that the user clicked on might now be used for another row due to the nature of cells being reused when you scroll up and down in the table. In other words we can't keep a pointer to that UITableViewCell. We need to find another way to target the cell and animate it if that row is visible and don't animate if the row is scrolled out of range.
Keep the cell object different from the object being animated so the cell holds a UIView. When the animation callback occurs check to make sure that the UIView still exists and, if it does, animate the changes.
When the cell object gets bumped off the screen and recycled, release the UIView that would have been animated and create a new one. When the animation callback occurs it will have nothing to do because the UIView no longer exists.
A modification of the above is to keep some sort of object in the UIView that your callback can check to see if the animation is still appropriate. This could be some sort of unique identifier for the picture being downloaded. If the identifier changes, no animation is needed. If it matches, do the animation.
EDIT:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyTableCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:MyIdentifier] autorelease];
} else {
UIView *oldViewToAnimate = [cell.contentView viewWithTag:1];
[oldViewToAnimate removeFromSuperview];
}
UIView *viewToAnimate = [[UIView alloc] initWithFrame:CGRectZero]; //replace with appropriate frame
viewToAnimate.tag = 1;
[cell.contentView addSubview:viewToAnimate];
return cell;
}
When you spawn your download process you pass in [cell.contentView viewWithTag:1]. When the download is done, it will update the appropriate view. If the table cell was reused the view will no longer have a superview and will not update the wrong cell.
There are things you can do to make this more efficient but this is the basic idea. If you have a custom UITableViewCell than this will probably look a bit different.
EDIT 2:
To reuse the viewToAnimate objects to make sure that they get updated if their parent cells were recycled, do something like the following:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyTableCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:MyIdentifier] autorelease];
} else {
UIView *oldViewToAnimate = [cell.contentView viewWithTag:1];
[oldViewToAnimate removeFromSuperview];
}
UIView *viewToAnimate = [self viewToAnimateForIndexPath:indexPath];
viewToAnimate.tag = 1;
[cell.contentView addSubview:viewToAnimate];
return cell;
}
viewToAnimateForIndexPath will need to:
Check to see if a viewToAnimate has been created for this indexPath
Create a viewToAnimate if there isn't one
Save a reference to the view that can be looked up by indexPath
Return the viewToAnimate so the table cell can use it
I don't know enough about your data structure to do this for you. Once the download process completes it can call this same method to get the view and animate it.